diff --git a/.gitignore b/.gitignore index c66ba3a..532eddc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,25 @@ -.env -.venv \ No newline at end of file +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.env \ No newline at end of file diff --git a/README.md b/README.md index 4caab5e..58beeac 100644 --- a/README.md +++ b/README.md @@ -1 +1,70 @@ -# communityLeaderboard \ No newline at end of file +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in your browser. + +The page will reload when you make changes.\ +You may also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can't go back!** + +If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. + +You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `npm run build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index 532eddc..0000000 --- a/client/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -.env \ No newline at end of file diff --git a/client/README.md b/client/README.md deleted file mode 100644 index 58beeac..0000000 --- a/client/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Getting Started with Create React App - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in your browser. - -The page will reload when you make changes.\ -You may also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can't go back!** - -If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. - -You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) - -### Analyzing the Bundle Size - -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) - -### Making a Progressive Web App - -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) - -### Advanced Configuration - -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) - -### Deployment - -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) - -### `npm run build` fails to minify - -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/client/public/manifest.json b/client/public/manifest.json deleted file mode 100644 index 080d6c7..0000000 --- a/client/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/client/src/App.css b/client/src/App.css deleted file mode 100644 index 8e80933..0000000 --- a/client/src/App.css +++ /dev/null @@ -1,408 +0,0 @@ -:root { - --NAV-COLOR: rgb(212, 212, 212); - --NAV-BORDER-COLOR: #525151; - --NAV-BGCOLOR: rgb(51, 55, 62); - --NAV-BORDER: 2px solid var(--NAV-BORDER-COLOR); - --CONTENT-H2-COLOR: rgb(212, 212, 212); - --CONTENT-COLOR: rgb(188, 197, 206); - --LINK-COLOR: white; - --LINK-HOVER-BGCOLOR: rgba(62, 216, 219, 0.866); - --LINK-FOCUS-BGCOLOR: rgb(57, 185, 188); - --LINK-ACTIVE-COLOR: rgb(82, 81, 81); -} -*{ - margin:0; - padding: 0; -} -body { - background-color: rgba(228, 251, 251, 0.866); -} - -/* NAVBAR *************************************************************************************** */ -.NavBar { - border-right: var(--NAV-BORDER); - padding: 1rem; - background-color: var(--NAV-BGCOLOR); - width: 18rem; - min-width: 14rem; - box-sizing: border-box; - overflow-y: auto; - position: fixed; - top: 0; - bottom: 0; - transition: width 0.3s ease, height 0.3s ease; -} - -.NavBar > h2 { - font-weight: 500; - color: var(--NAV-COLOR); -} - -.NavBar ul { - list-style-type: none; - padding: 0; - margin-top: 1rem; -} - -.NavBar li { - padding-bottom: 0.3rem; - margin-bottom: 1rem; - display: flex; - align-items: center; -} - -.NavBar li a, -.NavBar li span { - text-decoration: none; - color: var(--NAV-COLOR); - padding: 0.8rem; - border-bottom: 2px solid transparent; - flex-grow: 1; -} - -.NavBar li a:hover, -.NavBar li span:hover { - background-color: var(--LINK-HOVER-BGCOLOR); - color: rgb(253, 254, 254); - cursor: pointer; - border-radius: 7px; -} - -.NavBar li:active, -.NavBar li a:active, -.NavBar li span:active { - color: var(--LINK-ACTIVE-COLOR); -} - -.NavBar li:focus, -.NavBar li a:focus, -.NavBar li span:focus { - background-color: var(--LINK-FOCUS-BGCOLOR); - color: rgb(253, 254, 254); - outline-offset: 4px; - border-radius: 7px; -} - -.nav-icon { - margin-left: 0.5rem; - margin-right: 1rem; - font-size: 1.2rem; -} - -.ccsLogo { - height: 7.5rem; - margin-bottom: 2rem; - display: block; - margin-left: auto; /* Center horizontally */ - margin-right: auto; /* Center horizontally */ -} - -/* Scrollbar styles */ -.NavBar::-webkit-scrollbar, -body::-webkit-scrollbar { - width: 8px; -} - -.NavBar::-webkit-scrollbar-track, -body::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.1); -} - -.NavBar::-webkit-scrollbar-thumb, -body::-webkit-scrollbar-thumb { - background-color: rgb(82, 81, 81); - border-radius: 10px; -} - -.NavBar::-webkit-scrollbar-thumb:hover, -body::-webkit-scrollbar-thumb:hover { - cursor: pointer; -} - -.divider { - font-weight: 600; - pointer-events: none; - color: var(--CONTENT-H2-COLOR); -} - -/* APP-CONTAINER ********************************************************************************/ -.app-container { - display: flex; - flex-grow: 1; - max-width: 160rem; -} - -.content { - flex-grow: 1; - text-align: left; - padding: 2rem 12rem; - margin-top: 2rem; - margin-left: 13rem; - margin-right: 0; - color: var(--CONTENT-COLOR); - overflow-y: auto; -} - -.content h2 { - color: rgb(33, 35, 42); - font-weight: 700; - font-family: 'Roboto', sans-serif; -} - -.content p { - font-family: 'Open Sans', sans-serif; - line-height: 1.8; - margin-bottom: 1rem; -} - -/* Profile ************************************************/ -.profile-container { - display: flex; - justify-content: space-between; /* Space between containers */ - background-color: #282c34; - color: var(--CONTENT-COLOR); - padding: 2rem; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - /* Adjusted width to fit both containers */ - margin: 0 auto; - margin-top: 50px; /* Add margin-top for centering */ - position: relative; /* Ensure it's centered vertically */ -} - -.detail-container { - flex: 1; /* Flex to adjust with the container */ - text-align: center; - padding-right: 1rem; /* Padding to add space between containers */ -} - -.detail-container img { - border-radius: 50%; - width: 150px; - height: 150px; - object-fit: cover; - margin-bottom: 20px; - border: 3px solid var(--CONTENT-H2-COLOR); /* Add border */ - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); /* Add shadow */ -} - -.detail-container h1 { - font-size: 2.5em; /* Increase font size */ - color: white; /* Change name color to white */ - margin-bottom: 10px; -} - -.detail-container p { - font-size: 1.2em; - color: var(--CONTENT-COLOR); - margin: 5px 0; -} - -.detail-container p span { - font-weight: bold; - color: white; /* Ensure high contrast */ -} - -/* Loading and Error Styles */ -.loading, -.error { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - font-size: 1.5em; - color: rgb(33, 35, 42); -} - -/* Adding responsive design */ -@media (max-width: 768px) { - .profile-container { - padding: 1rem; - } - - .profile-container h1 { - font-size: 1.5em; - } - - .profile-container p { - font-size: 1em; - } -} - -.question-container { - flex: 2; /* Flex to adjust with the container */ -} - -.question-container h2 { - color: white; /* Change heading color to white */ - text-align: center; -} - -.questions-table { - width: 100%; - margin-top: 20px; - border-collapse: collapse; - background-color: var(--NAV-BGCOLOR); /* Add background color for contrast */ -} - -.questions-table th, -.questions-table td { - padding: 12px; - text-align: left; - border-bottom: 1px solid #ddd; -} - -.questions-table th { - background-color: var(--NAV-BGCOLOR); - color: white; -} - -.questions-table tr:hover { - background-color: var(--NAV-COLOR); - color: black; -} - -.questions-table a { - color: rgba(62, 216, 219, 0.866); - text-decoration: none; -} - -.questions-table a:hover { - text-decoration: underline; - color: rgb(57, 185, 255); -} - -/* LEADERBOARD ********************************************/ -.leaderboard-container { - background-color: var(--NAV-BGCOLOR); - border-radius: 0.9375rem; /* 15px in rem */ - padding: 1.25rem; /* 20px in rem */ - margin: 1.25rem; /* 20px in rem */ -} - -.leaderboard-title { - color: var(--CONTENT-H2-COLOR); - text-align: center; -} - -.leaderboard-table { - width: 100%; - border-collapse: collapse; - background-color: var(--NAV-BGCOLOR); - color: var(--CONTENT-COLOR); -} - -.leaderboard-table th, -.leaderboard-table td { - padding: 0.9375rem; /* 15px in rem */ - text-align: left; - border-bottom: 0.0625rem solid #ddd; /* 1px in rem */ -} - -.leaderboard-table th { - background-color: var(--NAV-BGCOLOR); - color: white; -} - -.leaderboard-row { - margin: 0.3125rem; /* 5px in rem */ -} - -.leaderboard-row td { - margin: 0.125rem; /* 2px in rem */ - background-color: var(--NAV-BGCOLOR); -} - -.leaderboard-row img { - border-radius: 50%; - width: 3.125rem; /* 50px in rem */ - height: 3.125rem; /* 50px in rem */ - object-fit: cover; -} - -.leaderboard-row td:last-child { - text-align: right; -} - -/* Responsive Navbar for Smaller Screens */ -@media (max-width: 1024px) { - .NavBar { - width: 100%; - height: 7rem; - position: fixed; - top: 0; - left: 0; - background-color: var(--NAV-BGCOLOR); - padding: 0.5rem; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-around; - z-index: 1000; /* Ensure it is above other content */ - border-right: none; /* Remove right border */ - border-bottom: var(--NAV-BORDER); /* Add bottom border */ - } - - .NavBar ul { - display: flex; - flex-direction: row; - align-items: right; - width: 70%; - justify-content: space-around; - } - - .NavBar li { - margin: 0; - padding: 0; - } - - .NavBar li a, - .NavBar li span { - padding: 0.5rem; - } - - .nav-icon { - font-size: 1.5rem; /* Increase icon size */ - margin: 0; - } - .ccsLogo{ - margin-top: 2rem; - height: 5rem; - margin-left: 0.5rem; - } - .NavBar > h2, - .NavBar li a span, - .NavBar li span span { - display: none; /* Hide text */ - } - .NavBar .divider{ - display: none; - } - .app-container { - margin-top: 3rem; /* Add top margin to avoid overlap with the navbar */ - margin-left: 0; /* Reset left margin */ - } - - .content { - margin-top: 1rem; - margin-left: 1rem; - margin-right: 1rem; - padding: 1rem; - } -} - -@media (max-width: 768px) { - .profile-container { - flex-direction: column; - align-items: center; - } - - .detail-container { - padding-right: 0; - margin-bottom: 1rem; /* Add space between the containers */ - } - - .question-container { - width: 100%; /* Ensure the question container takes full width */ - } -} diff --git a/client/src/Components/AuthVerify.js b/client/src/Components/AuthVerify.js deleted file mode 100644 index 203b2e5..0000000 --- a/client/src/Components/AuthVerify.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; - -const AuthVerify = ({ onVerify, setIsNewUser }) => { - const navigate = useNavigate(); - const location = useLocation(); - - useEffect(() => { - const query = new URLSearchParams(location.search); - const jwtToken = query.get('token'); - - if (jwtToken) { - console.log('JWT Token:', jwtToken); - - const requestBody = { - token: jwtToken, - }; - - console.log('Request Body:', requestBody); - - fetch('http://localhost:8000/api/auth/login/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(requestBody), - }) - .then(response => { - console.log('Response Status:', response.status); - return response.json().then(data => ({ status: response.status, data })); - }) - .then(({ status, data }) => { - console.log('Response Data:', data); - - if (status === 400) { - if (data.leetcode === false) { - setIsNewUser(true); - navigate('/username', { state: { jwtToken } }); // Pass the JWT token as state - } else { - throw new Error('Unexpected error format'); - } - } else if (status === 200) { - if (data && data.token) { - localStorage.setItem('token', data.token); // Save token locally - console.log('Token stored in localStorage:', localStorage.getItem('token')); // Log the stored token - onVerify(); - navigate('/profile'); - } else { - throw new Error('Token not found in response'); - } - } else { - throw new Error(`Unexpected response status: ${status}`); - } - }) - .catch(error => { - console.error('Error checking user:', error); - // Handle error (e.g., show a notification) - navigate('/login'); // Navigate to login page on error - }); - } else { - navigate('/login'); // Navigate to login page if no token found - } - }, [navigate, location, onVerify, setIsNewUser]); - - return
Loading...
; -}; - -export default AuthVerify; diff --git a/client/src/Components/Daily.js b/client/src/Components/Daily.js deleted file mode 100644 index 71814ef..0000000 --- a/client/src/Components/Daily.js +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import axios from 'axios'; - -import SERVER_URL from "../config.js"; -const BASE_URL = SERVER_URL+'/api/leaderboard'; -const Daily = () => { - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - axios.get(`${BASE_URL}/daily/`) - .then(response => { - const dataArray = Object.keys(response.data).map(key => ({ - id: key, - ...response.data[key] - })); - setData(dataArray); - setLoading(false); - }) - .catch(error => { - setError(error); - setLoading(false); - }); - }, []); - - if (loading) return

Loading...

; - if (error) return

Error loading data: {error.message}

; - - return ( -
-

Daily Leaderboard

- - - - - - - - - - - {data.map((item, index) => ( - - - - - - - ))} - -
RankUsernameQuestions Solved
{index + 1}Profile{item.username}{item.ques_solv}
-
- ); -}; - -export default Daily; diff --git a/client/src/Components/Monthly.js b/client/src/Components/Monthly.js deleted file mode 100644 index 8b75b58..0000000 --- a/client/src/Components/Monthly.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import axios from 'axios'; - -import SERVER_URL from "../config.js"; -const BASE_URL = SERVER_URL+'/api/leaderboard'; -const Monthly = () => { - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - axios.get(`${BASE_URL}/monthly/`) - .then(response => { - // Convert object to array of objects - const dataArray = Object.keys(response.data).map(key => ({ - id: key, - ...response.data[key] - })); - setData(dataArray); - setLoading(false); - }) - .catch(error => { - setError(error); - setLoading(false); - }); - }, []); - - if (loading) return

Loading...

; - if (error) return

Error loading data: {error.message}

; - - return ( -
-

Monthly Leaderboard

- - - - - - - - - - - {data.map((item, index) => ( - - - - - - - ))} - -
RankUsernameQuestions Solved
{index + 1}Profile{item.username}{item.ques_solv}
-
- ); -}; - -export default Monthly; diff --git a/client/src/Components/Navbar.js b/client/src/Components/Navbar.js deleted file mode 100644 index 3dc13bd..0000000 --- a/client/src/Components/Navbar.js +++ /dev/null @@ -1,41 +0,0 @@ -import { Link } from 'react-router-dom'; -import { FaCalendarDay, FaCalendarWeek, FaCalendarAlt } from 'react-icons/fa'; -import { MdAccountCircle } from 'react-icons/md'; -import ccsLogo from '../assets/ccs_logo.png'; - -export default function Navbar() { - return ( -
- ccs_logo - - -
- ); -} diff --git a/client/src/Components/UsernameEntry.js b/client/src/Components/UsernameEntry.js deleted file mode 100644 index 2ad45fb..0000000 --- a/client/src/Components/UsernameEntry.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; - -const UsernameEntry = () => { - const [leetcode_username, setLeetcodeUsername] = useState(''); - const navigate = useNavigate(); - const location = useLocation(); - const { jwtToken: stateJwtToken } = location.state || {}; // Retrieve the JWT token from the location state - const [jwtToken, setJwtToken] = useState(stateJwtToken || localStorage.getItem('token')); - - useEffect(() => { - const storedToken = localStorage.getItem('token'); - console.log('Retrieved token from localStorage:', storedToken); // Log the retrieved token - - if (!storedToken) { - navigate('/login'); // Navigate to login page if JWT token is missing - } else { - setJwtToken(storedToken); - } - }, [navigate]); - - const handleUsernameChange = (e) => { - setLeetcodeUsername(e.target.value); - }; - - const handleSubmit = (e) => { - e.preventDefault(); - - if (!jwtToken) { - console.error('JWT token is missing'); - navigate('/login'); // Navigate to login page if JWT token is missing - return; - } - - // Define the request payload - const payload = { - leetcode_username, - token: jwtToken - }; - - // Define the request options - const requestOptions = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${jwtToken}` - }, - body: JSON.stringify(payload) - }; - - // Make the API call - fetch('http://localhost:8000/api/auth/login/', requestOptions) - .then(response => response.json()) - .then(data => { - // Handle response - console.log('Success:', data); - // Navigate to the desired page - navigate('/profile'); - }) - .catch(error => { - console.error('Error:', error); - }); - }; - - return ( -
-

Enter Username

-
- - -
-
- ); -}; - -export default UsernameEntry; diff --git a/client/src/Components/Weekly.js b/client/src/Components/Weekly.js deleted file mode 100644 index 0b59b11..0000000 --- a/client/src/Components/Weekly.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import axios from 'axios'; - -import SERVER_URL from "../config.js"; -const BASE_URL = SERVER_URL+'/api/leaderboard'; -const Weekly = () => { - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - axios.get(`${BASE_URL}/weekly/`) - .then(response => { - // Convert object to array of objects - const dataArray = Object.keys(response.data).map(key => ({ - id: key, - ...response.data[key] - })); - setData(dataArray); - setLoading(false); - }) - .catch(error => { - setError(error); - setLoading(false); - }); - }, []); - - if (loading) return

Loading...

; - if (error) return

Error loading data: {error.message}

; - - return ( -
-

Weekly Leaderboard

- - - - - - - - - - - {data.map((item, index) => ( - - - - - - - ))} - -
RankUsernameQuestions Solved
{index + 1}Profile{item.username}{item.ques_solv}
-
- ); -}; - -export default Weekly; diff --git a/client/src/Login.css b/client/src/Login.css deleted file mode 100644 index 116d7c8..0000000 --- a/client/src/Login.css +++ /dev/null @@ -1,82 +0,0 @@ -body.login-body { - background-color: rgb(57, 185, 188); - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - margin: 0; - position: relative; - overflow: hidden; - } - - .login-outer-container { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - } - - .login-container { - display: flex; - flex-direction: column; - background-color: white; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - width: 100%; - max-width: 900px; - height: 60%; - z-index: 1; - } - - .logo-section { - flex: 1; - background-color: #f0f0f0; - display: flex; - justify-content: center; - align-items: center; - } - - .ccs-logo { - max-width: 90%; - height: auto; - } - - .form-section { - flex: 1; - padding: 40px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - } - - .form-section h2 { - text-align: center; - margin-bottom: 20px; - } - - .form-button { - width: 70%; - padding: 10px; - margin-top: 10px; - background-color: #4285f4; - color: white; - border: none; - border-radius: 8px; - cursor: pointer; - } - - .form-button:hover { - background-color: #357ae8; - } - - .ccs-login { - background-color: #34a853; - } - - .ccs-login:hover { - background-color: #2b8e42; - } - \ No newline at end of file diff --git a/client/src/config.js b/client/src/config.js deleted file mode 100644 index 3b0003e..0000000 --- a/client/src/config.js +++ /dev/null @@ -1,3 +0,0 @@ -const SERVER_URL = "http://127.0.0.1:8000"; - -export default SERVER_URL; \ No newline at end of file diff --git a/client/package-lock.json b/package-lock.json similarity index 97% rename from client/package-lock.json rename to package-lock.json index 3ddb052..dcb329b 100644 --- a/client/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "client", "version": "0.1.0", "dependencies": { + "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-alert-dialog": "^1.1.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -3324,6 +3326,334 @@ } } }, + "node_modules/@radix-ui/colors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", + "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.1.tgz", + "integrity": "sha512-wmCoJwj7byuVuiLKqDLlX7ClSUU0vd9sdCeM+2Ls+uf13+cpSJoMgwysHq1SGVVkJj5Xn0XWi1NoRCdkMpr6Mw==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dialog": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", + "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", + "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", + "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", + "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", @@ -5145,6 +5475,17 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -6994,6 +7335,11 @@ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "node_modules/detect-port-alt": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", @@ -8897,6 +9243,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", @@ -9567,6 +9921,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -15051,6 +15413,51 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.23.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", @@ -15153,6 +15560,28 @@ } } }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17202,16 +17631,16 @@ } }, "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { @@ -17361,6 +17790,47 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/client/package.json b/package.json similarity index 92% rename from client/package.json rename to package.json index 5f11c3b..0f48004 100644 --- a/client/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-alert-dialog": "^1.1.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 0000000..c7a085e --- /dev/null +++ b/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/public/ccs-bulb.png b/public/ccs-bulb.png new file mode 100644 index 0000000..4f907f3 Binary files /dev/null and b/public/ccs-bulb.png differ diff --git a/client/public/favicon.ico b/public/favicon.ico similarity index 100% rename from client/public/favicon.ico rename to public/favicon.ico diff --git a/client/public/index.html b/public/index.html similarity index 89% rename from client/public/index.html rename to public/index.html index aa069f2..9aee3da 100644 --- a/client/public/index.html +++ b/public/index.html @@ -2,14 +2,14 @@ - - + + - + - React App + Codeboard diff --git a/client/public/logo512.png b/public/logo512.png similarity index 100% rename from client/public/logo512.png rename to public/logo512.png diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..6822063 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Codeboard", + "name": "CCS Codeboard", + "icons": [ + { + "src": "ccs-bulb.png", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/client/public/robots.txt b/public/robots.txt similarity index 100% rename from client/public/robots.txt rename to public/robots.txt diff --git a/server/api_docs.txt b/server/api_docs.txt deleted file mode 100644 index 3bf5ec8..0000000 --- a/server/api_docs.txt +++ /dev/null @@ -1,21 +0,0 @@ -1. Create new leetcode user acc in database(POST request) -api endpoint: /register_user/ -Json Format: -{ - 'username' : 'example_username' -} - -2. Get all users info from database(GET request) -api endpoint: /get_leetcode/ - -3. Get single user info from database(GET request) -api endpoint: /get_user/ -key:username -value:example_username - -for ex: /get_user/?username=example_username - -4. Get info for today's questions for a user -api endpoint: /today_questions/ -key:username (leetcode username) -forex: /get_user/?username=example_username \ No newline at end of file diff --git a/server/app/__init__.py b/server/app/__init__.py deleted file mode 100644 index 9e0d95f..0000000 --- a/server/app/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .celery import app as celery_app - -__all__ = ('celery_app',) \ No newline at end of file diff --git a/server/app/__pycache__/__init__.cpython-312.pyc b/server/app/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 6a55cb6..0000000 Binary files a/server/app/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/server/app/__pycache__/celery.cpython-312.pyc b/server/app/__pycache__/celery.cpython-312.pyc deleted file mode 100644 index 904c724..0000000 Binary files a/server/app/__pycache__/celery.cpython-312.pyc and /dev/null differ diff --git a/server/app/__pycache__/settings.cpython-312.pyc b/server/app/__pycache__/settings.cpython-312.pyc deleted file mode 100644 index d492703..0000000 Binary files a/server/app/__pycache__/settings.cpython-312.pyc and /dev/null differ diff --git a/server/app/__pycache__/urls.cpython-312.pyc b/server/app/__pycache__/urls.cpython-312.pyc deleted file mode 100644 index 38b91c6..0000000 Binary files a/server/app/__pycache__/urls.cpython-312.pyc and /dev/null differ diff --git a/server/app/__pycache__/wsgi.cpython-312.pyc b/server/app/__pycache__/wsgi.cpython-312.pyc deleted file mode 100644 index 8db4b94..0000000 Binary files a/server/app/__pycache__/wsgi.cpython-312.pyc and /dev/null differ diff --git a/server/app/asgi.py b/server/app/asgi.py deleted file mode 100644 index c8d5aaa..0000000 --- a/server/app/asgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -ASGI config for app project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ -""" - -import os - -from django.core.asgi import get_asgi_application - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') - -application = get_asgi_application() diff --git a/server/app/celery.py b/server/app/celery.py deleted file mode 100644 index 882b8bc..0000000 --- a/server/app/celery.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import absolute_import, unicode_literals -import os -from celery import Celery -from django.conf import settings -# set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') - -app = Celery('app') -app.conf.enable_utc = False -app.conf.update(timezone='Asia/Kolkata') - -app.config_from_object(settings, namespace='CELERY') - -# Celery-Beat settings -app.conf.beat_schedule = { - 'refresh_user_data':{ - 'task': 'leaderboard.tasks.refresh_user_data', - 'schedule': 300, - }, -} - -# Load task modules from all registered Django app configs. -app.autodiscover_tasks() - -@app.task(bind=True) -def debug_task(self): - print(f'Request: {self.request!r}') \ No newline at end of file diff --git a/server/app/settings.py b/server/app/settings.py deleted file mode 100644 index dccb435..0000000 --- a/server/app/settings.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Django settings for app project. - -Generated by 'django-admin startproject' using Django 4.2.13. - -For more information on this file, see -https://docs.djangoproject.com/en/4.2/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.2/ref/settings/ -""" - -from pathlib import Path -from dotenv import load_dotenv - -import os - -load_dotenv() - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv('SECRET_KEY') - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'rest_framework', - 'rest_framework.authtoken', - 'corsheaders', - 'ccs_auth', - 'leaderboard', - 'django_celery_results', - 'django_celery_beat', -] - -CORS_ORIGIN_ALLOW_ALL = True - -MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'app.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'app.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/4.2/ref/settings/#databases - - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': os.getenv('DB_NAME'), - 'USER': os.getenv('DB_USER'), - 'PASSWORD': os.getenv('DB_PASS'), - 'HOST': os.getenv('DB_HOST'), - 'PORT': os.getenv('DB_PORT'), - } -} - - -# Password validation -# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - }, - }, - 'root': { - 'handlers': ['console'], - 'level': 'INFO', - }, -} -# Internationalization -# https://docs.djangoproject.com/en/4.2/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'Asia/Kolkata' - -USE_I18N = True - -USE_TZ = True - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.2/howto/static-files/ - -STATIC_URL = 'static/' - -# Default primary key field type -# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - -# Celery settings - -CELERY_BROKER_URL = 'redis://127.0.0.1:6379' -CELERY_RESULT_BACKEND = 'django-db' -CELERY_ACCEPT_CONTENT = ['application/json'] -task_serializer = 'json' -CELERY_RESULT_SERIALIZER = 'json' -timezone = 'Asia/Kolkata' - -# Celery-Beat settings -CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' - -REST_FRAMEWORK = { - 'DEFAULT_RENDERER_CLASSES': [ - 'rest_framework.renderers.JSONRenderer', - ], - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.TokenAuthentication', - ) -} - -AUTHENTICATION_BACKENDS = [ - 'ccs_auth.auth_backends.SSOAuthenticationBackend', - 'django.contrib.auth.backends.ModelBackend', # Default backend -] - -AUTH_USER_MODEL = 'ccs_auth.CUser' - diff --git a/server/app/urls.py b/server/app/urls.py deleted file mode 100644 index cb77a05..0000000 --- a/server/app/urls.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -URL configuration for app project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/4.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" -from django.contrib import admin -from django.urls import path, include - -urlpatterns = [ - path('admin/', admin.site.urls), - path('api/leaderboard/', include('leaderboard.urls')), - path('api/auth/', include('ccs_auth.urls')), -] diff --git a/server/app/wsgi.py b/server/app/wsgi.py deleted file mode 100644 index ef30895..0000000 --- a/server/app/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for app project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') - -application = get_wsgi_application() diff --git a/server/ccs_auth/__init__.py b/server/ccs_auth/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/ccs_auth/__pycache__/__init__.cpython-312.pyc b/server/ccs_auth/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index c0c282f..0000000 Binary files a/server/ccs_auth/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/admin.cpython-312.pyc b/server/ccs_auth/__pycache__/admin.cpython-312.pyc deleted file mode 100644 index 7bf6284..0000000 Binary files a/server/ccs_auth/__pycache__/admin.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/apps.cpython-312.pyc b/server/ccs_auth/__pycache__/apps.cpython-312.pyc deleted file mode 100644 index 2448e3f..0000000 Binary files a/server/ccs_auth/__pycache__/apps.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/auth_backends.cpython-312.pyc b/server/ccs_auth/__pycache__/auth_backends.cpython-312.pyc deleted file mode 100644 index 76a1f16..0000000 Binary files a/server/ccs_auth/__pycache__/auth_backends.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/managers.cpython-312.pyc b/server/ccs_auth/__pycache__/managers.cpython-312.pyc deleted file mode 100644 index 536835d..0000000 Binary files a/server/ccs_auth/__pycache__/managers.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/middleware.cpython-312.pyc b/server/ccs_auth/__pycache__/middleware.cpython-312.pyc deleted file mode 100644 index 612c683..0000000 Binary files a/server/ccs_auth/__pycache__/middleware.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/models.cpython-312.pyc b/server/ccs_auth/__pycache__/models.cpython-312.pyc deleted file mode 100644 index 00fe0da..0000000 Binary files a/server/ccs_auth/__pycache__/models.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/serializers.cpython-312.pyc b/server/ccs_auth/__pycache__/serializers.cpython-312.pyc deleted file mode 100644 index ff91350..0000000 Binary files a/server/ccs_auth/__pycache__/serializers.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/urls.cpython-312.pyc b/server/ccs_auth/__pycache__/urls.cpython-312.pyc deleted file mode 100644 index 261e1b2..0000000 Binary files a/server/ccs_auth/__pycache__/urls.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/__pycache__/views.cpython-312.pyc b/server/ccs_auth/__pycache__/views.cpython-312.pyc deleted file mode 100644 index cd7e6ad..0000000 Binary files a/server/ccs_auth/__pycache__/views.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/admin.py b/server/ccs_auth/admin.py deleted file mode 100644 index 4f72786..0000000 --- a/server/ccs_auth/admin.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.contrib import admin -from .models import CUser - -class CUserAdmin(admin.ModelAdmin): - list_display = ('email', 'first_name', 'last_name', 'is_active', 'is_admin', 'is_staff', 'is_superuser') - list_filter = ('is_admin', 'is_staff', 'is_superuser') - search_fields = ('email', 'first_name', 'last_name', 'roll_no') - ordering = ('email',) - exclude = ('password',) - - def get_form(self, request, obj=None, **kwargs): - form = super(CUserAdmin, self).get_form(request, obj, **kwargs) - form.base_fields.pop('password', None) # Exclude the password field from the form - return form - -admin.site.register(CUser, CUserAdmin) \ No newline at end of file diff --git a/server/ccs_auth/apps.py b/server/ccs_auth/apps.py deleted file mode 100644 index 8cfae8e..0000000 --- a/server/ccs_auth/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class CcsAuthConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'ccs_auth' diff --git a/server/ccs_auth/auth_backends.py b/server/ccs_auth/auth_backends.py deleted file mode 100644 index 0936dba..0000000 --- a/server/ccs_auth/auth_backends.py +++ /dev/null @@ -1,48 +0,0 @@ -from django.contrib.auth.backends import BaseBackend -from .models import CUser as CustomUser -from dotenv import load_dotenv -import os, jwt -load_dotenv() - -class SSOAuthenticationBackend(BaseBackend): - def authenticate(self, request, sso_token=None): - if sso_token is None: - return None - - user_info = self.validate_sso_token(sso_token) - if user_info: - try: - user = CustomUser.objects.get(pk=user_info['email']) - except CustomUser.DoesNotExist: - first_name, last_name = user_info['name'].split(' ') - print("authentication backend: ", user_info) - user = CustomUser.objects.create( - id=user_info['_id'], - email=user_info['email'], - first_name=first_name, - last_name=last_name, - roll_no=user_info['rollNo'] - ) - print("authentication backend: ", user) - return user - return None - - def get_user(self, user_id): - try: - return CustomUser.objects.get(id=user_id) - except: - pass - try: - return CustomUser.objects.get(email=user_id) - except CustomUser.DoesNotExist: - return None - - def validate_sso_token(self, sso_token): - jwt_secret = os.getenv('CLIENT_SECRET') - try: - payload = jwt.decode(sso_token, jwt_secret, algorithms=['HS256']) - return payload - except jwt.ExpiredSignatureError: - return None - except jwt.InvalidTokenError: - return None diff --git a/server/ccs_auth/managers.py b/server/ccs_auth/managers.py deleted file mode 100644 index 9eb4c69..0000000 --- a/server/ccs_auth/managers.py +++ /dev/null @@ -1,24 +0,0 @@ -# managers.py -from django.contrib.auth.models import BaseUserManager - -class CUserManager(BaseUserManager): - def create_user(self, email, password=None, **extra_fields): - if not email: - raise ValueError('The Email field must be set') - email = self.normalize_email(email) - user = self.model(email=email, **extra_fields) - user.set_password(password) - user.save(using=self._db) - return user - - def create_superuser(self, email, password=None, **extra_fields): - extra_fields.setdefault('is_staff', True) - extra_fields.setdefault('is_superuser', True) - extra_fields.setdefault('is_admin', True) - - if extra_fields.get('is_staff') is not True: - raise ValueError('Superuser must have is_staff=True.') - if extra_fields.get('is_superuser') is not True: - raise ValueError('Superuser must have is_superuser=True.') - - return self.create_user(email, password, **extra_fields) diff --git a/server/ccs_auth/migrations/0001_initial.py b/server/ccs_auth/migrations/0001_initial.py deleted file mode 100644 index 52cec89..0000000 --- a/server/ccs_auth/migrations/0001_initial.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-23 20:28 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ] - - operations = [ - ] diff --git a/server/ccs_auth/migrations/0002_initial.py b/server/ccs_auth/migrations/0002_initial.py deleted file mode 100644 index 2218aca..0000000 --- a/server/ccs_auth/migrations/0002_initial.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-23 20:29 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('ccs_auth', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='CUser', - fields=[ - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('id', models.CharField(blank=True, max_length=100, unique=True)), - ('email', models.EmailField(max_length=254, primary_key=True, serialize=False, unique=True)), - ('roll_no', models.CharField(blank=True, max_length=10, null=True)), - ('first_name', models.CharField(max_length=50)), - ('last_name', models.CharField(max_length=50)), - ('is_active', models.BooleanField(default=True)), - ('is_admin', models.BooleanField(default=True)), - ('is_staff', models.BooleanField(default=True)), - ('is_superuser', models.BooleanField(default=True)), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/server/ccs_auth/migrations/0003_alter_cuser_is_admin_alter_cuser_is_staff_and_more.py b/server/ccs_auth/migrations/0003_alter_cuser_is_admin_alter_cuser_is_staff_and_more.py deleted file mode 100644 index 1e85785..0000000 --- a/server/ccs_auth/migrations/0003_alter_cuser_is_admin_alter_cuser_is_staff_and_more.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-23 21:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ccs_auth', '0002_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='cuser', - name='is_admin', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='cuser', - name='is_staff', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='cuser', - name='is_superuser', - field=models.BooleanField(default=False), - ), - ] diff --git a/server/ccs_auth/migrations/__init__.py b/server/ccs_auth/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/ccs_auth/migrations/__pycache__/0001_initial.cpython-312.pyc b/server/ccs_auth/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 41dea6b..0000000 Binary files a/server/ccs_auth/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/migrations/__pycache__/0002_initial.cpython-312.pyc b/server/ccs_auth/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index cedf86d..0000000 Binary files a/server/ccs_auth/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/migrations/__pycache__/0003_alter_cuser_is_admin_alter_cuser_is_staff_and_more.cpython-312.pyc b/server/ccs_auth/migrations/__pycache__/0003_alter_cuser_is_admin_alter_cuser_is_staff_and_more.cpython-312.pyc deleted file mode 100644 index b70ec15..0000000 Binary files a/server/ccs_auth/migrations/__pycache__/0003_alter_cuser_is_admin_alter_cuser_is_staff_and_more.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/migrations/__pycache__/__init__.cpython-312.pyc b/server/ccs_auth/migrations/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index e14d9ce..0000000 Binary files a/server/ccs_auth/migrations/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/server/ccs_auth/models.py b/server/ccs_auth/models.py deleted file mode 100644 index fabd46b..0000000 --- a/server/ccs_auth/models.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.db import models -from django.contrib.auth.models import AbstractBaseUser -from .managers import CUserManager - -class CUser(AbstractBaseUser): - id = models.CharField(max_length=100, unique=True, blank=True) - email = models.EmailField(unique=True, blank=False, null=False, primary_key=True) - roll_no = models.CharField(max_length=10, unique=False, blank=True, null=True) - first_name = models.CharField(max_length=50) - last_name = models.CharField(max_length=50) - - is_active = models.BooleanField(default=True) - is_admin = models.BooleanField(default=False) - is_staff = models.BooleanField(default=False) - is_superuser = models.BooleanField(default=False) - - objects = CUserManager() - - USERNAME_FIELD = 'email' - - def __str__(self): - return self.email - - def has_perm(self, perm, obj=None): - return self.is_admin - - def has_module_perms(self, app_label): - return True diff --git a/server/ccs_auth/serializers.py b/server/ccs_auth/serializers.py deleted file mode 100644 index a375d10..0000000 --- a/server/ccs_auth/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -from rest_framework import serializers -from rest_framework.authtoken.models import Token -from .models import * - -class CUserSerializer(serializers.ModelSerializer): - class Meta(object): - model = CUser - fields = ('id', 'email', 'first_name', 'last_name','roll_no') - - def create(self, validated_data): - user = super().create(validated_data) - Token.objects.create(user=user) - return user - \ No newline at end of file diff --git a/server/ccs_auth/tests.py b/server/ccs_auth/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/server/ccs_auth/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/server/ccs_auth/urls.py b/server/ccs_auth/urls.py deleted file mode 100644 index 8215f19..0000000 --- a/server/ccs_auth/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.urls import path -from . import views - -urlpatterns = [ - path('login/', views.LoginView.as_view(), name='login'), - path('logout/', views.LogoutView.as_view(), name='logout'), -] diff --git a/server/ccs_auth/views.py b/server/ccs_auth/views.py deleted file mode 100644 index f8d0206..0000000 --- a/server/ccs_auth/views.py +++ /dev/null @@ -1,85 +0,0 @@ -from rest_framework import status -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework.authtoken.models import Token -from rest_framework.permissions import IsAuthenticated - -from django.contrib.auth import authenticate, login, logout -from dotenv import load_dotenv - -from leaderboard.models import Leetcode -from .serializers import * -from .models import * - -import os, requests -load_dotenv() - -API_URL = 'http://127.0.0.1:8000/api/leaderboard' - -class LoginView(APIView): - def post(self, request): - sso_token = request.data.get('token') - leetcode_username = request.data.get('leetcode_username') - - # Authenticate user with SSO token - user = authenticate(request, sso_token=sso_token) - if not user: - return Response({'error': 'Invalid Credentials'}, status=status.HTTP_400_BAD_REQUEST) - - # Login user - login(request, user) - print(f"User {user} logged in successfully with ID: {user.pk}") - - # Generate or retrieve token for the user - token, _ = Token.objects.get_or_create(user=user) - - # Serialize user data - serializer = CUserSerializer(instance=user) - - try: - # Check if the user already has a linked Leetcode account - leetcode = user.leetcode - print(f"Leetcode account already exists for {user} as {leetcode.username}") - return Response({'token': token.key, 'user': serializer.data}, status=status.HTTP_200_OK) - except Leetcode.DoesNotExist: - print(f"Leetcode account does not exist for {user}") - if not leetcode_username: - error = { - 'error': 'Leetcode username is required', - 'message': 'Please provide a Leetcode username to link to your account', - 'leetcode': False - } - return Response(error, status=status.HTTP_400_BAD_REQUEST) - - try: - # Check if the provided Leetcode username already exists - if Leetcode.objects.filter(username=leetcode_username).exists(): - return Response({'error': 'Leetcode account already exists'}, status=status.HTTP_400_BAD_REQUEST) - - # Attempt to register the Leetcode account via API - res = requests.post( - f"{API_URL}/register/", - data={'username': leetcode_username}, - headers={"Authorization": f"Token {token.key}"} - ) - - if res.status_code == 200 or res.status_code == 201: - print("Registration request successful") - user.leetcode = Leetcode.objects.filter(username=leetcode_username).first() - else: - print(f"Registration request failed: {res.status_code} - {res.text}") - - except Exception as e: - print(f"Error making request to /register/: {e}") - - # Save user with updated Leetcode account reference if created - user.save() - - return Response({'token': token.key, 'user': serializer.data}, status=status.HTTP_200_OK) - -class LogoutView(APIView): - permission_classes = [IsAuthenticated] - def get(self, request): - token =Token.objects.get(user=request.user) - token.delete() - return Response({'message': 'Logged out successfully'}, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/server/get_leetcode_testing.py b/server/get_leetcode_testing.py deleted file mode 100644 index 43aaa4d..0000000 --- a/server/get_leetcode_testing.py +++ /dev/null @@ -1,78 +0,0 @@ -from django.db.models.signals import post_save -from django.dispatch import receiver -import requests - -# Define your queries as constants at the top of your file -MATCHED_USER_QUERY = ''' - query userPublicProfile($username: String!) { - matchedUser(username: $username) { - profile { - ranking - userAvatar - realName - } - } - } -''' - -QUESTIONS_SUBMITTED_QUERY = ''' - query recentAcSubmissions($username: String!, $limit: Int!) { - recentAcSubmissionList(username: $username, limit: $limit) { - id - } - } -''' - -LANGUAGE_PROBLEM_COUNT_QUERY = ''' - query languageStats($username: String!) { - matchedUser(username: $username) { - languageProblemCount { - languageName - problemsSolved - } - } - } -''' - -# Modify your send_query function to accept variables -def send_query(query, variables): - url = "https://leetcode.com/graphql" - headers = {"Content-Type": "application/json"} - response = requests.post(url, json={"query": query, "variables": variables}, headers=headers) - data = response.json() - return data - -# Use the modified send_query function in your signal -def get_user_data(username): - limit = 500 - - response = send_query(MATCHED_USER_QUERY, {"username": username}) - response2 = send_query(QUESTIONS_SUBMITTED_QUERY, {"username": username, "limit": limit}) - response3 = send_query(LANGUAGE_PROBLEM_COUNT_QUERY, {"username": username}) - - data = response - data2 = response2 - data3 = response3 - print(response2) - - data2 = data2['data']['recentAcSubmissionList'] - - questions_solved = [] - for question in data2: - questions_solved.append(question['id']) - - profile = data['data']['matchedUser']['profile'] - realname = profile['realName'] - rank = profile['ranking'] - photo_url = profile['userAvatar'] - last_solved = "Not available" - number_of_questions = 0 - data3 = data3['data']['matchedUser']['languageProblemCount'] - for question in data3: - number_of_questions += question['problemsSolved'] - - print(questions_solved) - - print(f"Data for {username} saved successfully") - -get_user_data("singlaishan69") \ No newline at end of file diff --git a/server/leaderboard/__init__.py b/server/leaderboard/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/leaderboard/__pycache__/__init__.cpython-312.pyc b/server/leaderboard/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index b363d0a..0000000 Binary files a/server/leaderboard/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/admin.cpython-312.pyc b/server/leaderboard/__pycache__/admin.cpython-312.pyc deleted file mode 100644 index c5ec763..0000000 Binary files a/server/leaderboard/__pycache__/admin.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/apps.cpython-312.pyc b/server/leaderboard/__pycache__/apps.cpython-312.pyc deleted file mode 100644 index c8cd600..0000000 Binary files a/server/leaderboard/__pycache__/apps.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/fetch_leetcode_data.cpython-312.pyc b/server/leaderboard/__pycache__/fetch_leetcode_data.cpython-312.pyc deleted file mode 100644 index 36b0a23..0000000 Binary files a/server/leaderboard/__pycache__/fetch_leetcode_data.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/models.cpython-312.pyc b/server/leaderboard/__pycache__/models.cpython-312.pyc deleted file mode 100644 index d311afc..0000000 Binary files a/server/leaderboard/__pycache__/models.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/query_manager.cpython-312.pyc b/server/leaderboard/__pycache__/query_manager.cpython-312.pyc deleted file mode 100644 index c195ddc..0000000 Binary files a/server/leaderboard/__pycache__/query_manager.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/serializers.cpython-312.pyc b/server/leaderboard/__pycache__/serializers.cpython-312.pyc deleted file mode 100644 index a064f17..0000000 Binary files a/server/leaderboard/__pycache__/serializers.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/signals.cpython-312.pyc b/server/leaderboard/__pycache__/signals.cpython-312.pyc deleted file mode 100644 index 7082b90..0000000 Binary files a/server/leaderboard/__pycache__/signals.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/tasks.cpython-312.pyc b/server/leaderboard/__pycache__/tasks.cpython-312.pyc deleted file mode 100644 index 8bc84c1..0000000 Binary files a/server/leaderboard/__pycache__/tasks.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/urls.cpython-312.pyc b/server/leaderboard/__pycache__/urls.cpython-312.pyc deleted file mode 100644 index 50c9e16..0000000 Binary files a/server/leaderboard/__pycache__/urls.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/__pycache__/views.cpython-312.pyc b/server/leaderboard/__pycache__/views.cpython-312.pyc deleted file mode 100644 index f4743cb..0000000 Binary files a/server/leaderboard/__pycache__/views.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/admin.py b/server/leaderboard/admin.py deleted file mode 100644 index 717e965..0000000 --- a/server/leaderboard/admin.py +++ /dev/null @@ -1,51 +0,0 @@ -from django.contrib import admin -from django.db.models import Case, When, Value, IntegerField -from django.http import HttpRequest -from leaderboard.models import * - -class RankFilter(admin.SimpleListFilter): - title = 'Monthly Rank' - parameter_name = 'monthly_rank' - - def lookups(self, request, model_admin): - return ( - ('non_zero', 'Non-zero Ranks'), - ('zero', 'Zero Ranks'), - ) - - def queryset(self, request, queryset): - if self.value() == 'non_zero': - return queryset.exclude(monthly_rank=0).order_by('monthly_rank') - if self.value() == 'zero': - return queryset.filter(monthly_rank=0) - # Default ordering: non-zero ranks in ascending order, followed by zero ranks - return queryset.annotate( - custom_order=Case( - When(monthly_rank=0, then=Value(1)), - default=Value(0), - output_field=IntegerField(), - ) - ).order_by('custom_order', 'monthly_rank') - -class LeetcodeAdmin(admin.ModelAdmin): - list_display = ('username', 'monthly_rank', 'matched_ques') - list_filter = (RankFilter,) - def has_add_permission(self, request, obj=None): - return False - -class QuestionAdmin(admin.ModelAdmin): - list_display = ('leetcode_id', 'title', 'questionDate','difficulty') - list_filter = ('questionDate', 'difficulty') - -class LeaderboardEntryAdmin(admin.ModelAdmin): - def has_add_permission(self, request, obj=None): - return False - -class LeaderboardAdmin(admin.ModelAdmin): - def has_add_permission(self, request, obj=None): - return False - -admin.site.register(Leetcode, LeetcodeAdmin) -admin.site.register(Question, QuestionAdmin) -admin.site.register(LeaderboardEntry, LeaderboardEntryAdmin) -admin.site.register(Leaderboard, LeaderboardAdmin) \ No newline at end of file diff --git a/server/leaderboard/apps.py b/server/leaderboard/apps.py deleted file mode 100644 index 590a452..0000000 --- a/server/leaderboard/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class LeaderboardConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'leaderboard' diff --git a/server/leaderboard/migrations/0001_initial.py b/server/leaderboard/migrations/0001_initial.py deleted file mode 100644 index 52cec89..0000000 --- a/server/leaderboard/migrations/0001_initial.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-23 20:28 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ] - - operations = [ - ] diff --git a/server/leaderboard/migrations/0002_initial.py b/server/leaderboard/migrations/0002_initial.py deleted file mode 100644 index 81e9a47..0000000 --- a/server/leaderboard/migrations/0002_initial.py +++ /dev/null @@ -1,70 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-23 20:29 - -import datetime -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('leaderboard', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Leaderboard', - fields=[ - ('leaderboard_key', models.AutoField(primary_key=True, serialize=False)), - ('leaderboard_type', models.CharField(choices=[('daily', 'daily'), ('weekly', 'weekly'), ('monthly', 'monthly')], default='daily', max_length=20)), - ('leaderboard_data', models.JSONField(blank=True, default=dict, null=True)), - ], - ), - migrations.CreateModel( - name='Question', - fields=[ - ('question_key', models.AutoField(primary_key=True, serialize=False)), - ('leetcode_id', models.IntegerField(default=0)), - ('title', models.CharField(default='', max_length=100)), - ('titleSlug', models.CharField(default='', max_length=100)), - ('questionDate', models.DateTimeField(default=datetime.datetime.now)), - ('difficulty', models.CharField(choices=[('Basic', 'Basic'), ('Intermidiate', 'Intermidiate'), ('Advanced', 'Advanced')], default='Basic', max_length=20)), - ], - ), - migrations.CreateModel( - name='Leetcode', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('username', models.CharField(default='', max_length=100, unique=True)), - ('name', models.CharField(blank=True, default='Scraping..', max_length=100, null=True)), - ('leetcode_rank', models.CharField(blank=True, default='Scraping..', max_length=10, null=True)), - ('daily_rank', models.IntegerField(blank=True, default=0, null=True)), - ('weekly_rank', models.IntegerField(blank=True, default=0, null=True)), - ('monthly_rank', models.IntegerField(blank=True, default=0, null=True)), - ('photo_url', models.CharField(blank=True, default='Scarping..', max_length=200, null=True)), - ('total_solved', models.IntegerField(blank=True, default=0, null=True)), - ('matched_ques', models.IntegerField(blank=True, default=0, null=True)), - ('submission_dict', models.JSONField(blank=True, default=dict, null=True)), - ('total_solved_dict', models.JSONField(blank=True, default=dict, null=True)), - ('matched_ques_dict', models.JSONField(blank=True, default=dict, null=True)), - ('user', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='LeaderboardEntry', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('interval', models.CharField(max_length=10)), - ('questions_solved', models.IntegerField(default=0)), - ('earliest_solved_timestamp', models.BigIntegerField(default=0)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='leaderboard.leetcode')), - ], - options={ - 'unique_together': {('user', 'interval')}, - }, - ), - ] diff --git a/server/leaderboard/migrations/__init__.py b/server/leaderboard/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/leaderboard/migrations/__pycache__/0001_initial.cpython-312.pyc b/server/leaderboard/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index 90b23c4..0000000 Binary files a/server/leaderboard/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/migrations/__pycache__/0002_initial.cpython-312.pyc b/server/leaderboard/migrations/__pycache__/0002_initial.cpython-312.pyc deleted file mode 100644 index a601ae2..0000000 Binary files a/server/leaderboard/migrations/__pycache__/0002_initial.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/migrations/__pycache__/__init__.cpython-312.pyc b/server/leaderboard/migrations/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 2e0245f..0000000 Binary files a/server/leaderboard/migrations/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/server/leaderboard/models.py b/server/leaderboard/models.py deleted file mode 100644 index 219f238..0000000 --- a/server/leaderboard/models.py +++ /dev/null @@ -1,55 +0,0 @@ -from django.db import models -from django.contrib.postgres.fields import ArrayField -from ccs_auth.models import CUser -import datetime - -class Leetcode(models.Model): - user = models.OneToOneField(CUser, on_delete=models.CASCADE, null=True, blank=True,default=None ) - id = models.AutoField(primary_key=True) - username = models.CharField(max_length=100, null=False, blank=False, unique=True, default="") - name = models.CharField(max_length=100, null=True, blank=True, default="Scraping..") - leetcode_rank= models.CharField(max_length=10, null=True, blank=True, default="Scraping..") - - daily_rank = models.IntegerField(null=True, blank=True, default=0) - weekly_rank = models.IntegerField(null=True, blank=True, default=0) - monthly_rank = models.IntegerField(null=True, blank=True, default=0) - - photo_url = models.CharField(max_length=200, null=True, blank=True, default="Scarping..") - - total_solved = models.IntegerField(null=True, blank=True, default=0) - matched_ques = models.IntegerField(null=True, blank=True, default=0) - - submission_dict = models.JSONField(null=True, blank=True, default=dict) - total_solved_dict = models.JSONField(null=True, blank=True, default=dict) - matched_ques_dict = models.JSONField(null=True, blank=True, default=dict) - - REQUIRED_FIELDS = ['username'] - def __str__(self): - return self.username - -class Question(models.Model): - question_key = models.AutoField(primary_key=True) - leetcode_id = models.IntegerField(null=False, blank=False, default=0) - title = models.CharField(max_length=100, null=False, blank=False, default="") - titleSlug = models.CharField(max_length=100, null=False, blank=False, default="") - questionDate = models.DateTimeField(blank=False,default=datetime.datetime.now) - difficulty = models.CharField(max_length=20, null=False, blank=False, choices=[('Basic', 'Basic'), ('Intermidiate', 'Intermidiate'), ('Advanced', 'Advanced')], default='Basic') - def __str__(self): - return self.title - -class LeaderboardEntry(models.Model): - user = models.ForeignKey(Leetcode, on_delete=models.CASCADE) - interval = models.CharField(max_length=10) # 'day', 'week', 'month' - questions_solved = models.IntegerField(null=False, default=0) - earliest_solved_timestamp = models.BigIntegerField(null=False, default=0) - - class Meta: - unique_together = ('user', 'interval') - -class Leaderboard(models.Model): - leaderboard_key = models.AutoField(primary_key=True) - leaderboard_type = models.CharField(max_length=20, null=False, blank=False, choices=[('daily', 'daily'), ('weekly', 'weekly'), ('monthly', 'monthly')], default='daily') - leaderboard_data = models.JSONField(null=True, blank=True, default=dict) - - def __str__(self): - return self.leaderboard_type \ No newline at end of file diff --git a/server/leaderboard/query_manager.py b/server/leaderboard/query_manager.py deleted file mode 100644 index 5c313e8..0000000 --- a/server/leaderboard/query_manager.py +++ /dev/null @@ -1,68 +0,0 @@ -import requests -from .models import Leetcode - -MATCHED_USER_QUERY = ''' - query userPublicProfile($username: String!) { - matchedUser(username: $username) { - profile { - ranking - userAvatar - realName - } - } - } -''' - -QUESTIONS_SUBMITTED_QUERY = ''' - query recentAcSubmissions($username: String!, $limit: Int!) { - recentAcSubmissionList(username: $username, limit: $limit) { - titleSlug - timestamp - } - } -''' - -LANGUAGE_PROBLEM_COUNT_QUERY = ''' - query languageStats($username: String!) { - matchedUser(username: $username) { - languageProblemCount { - languageName - problemsSolved - } - } - } -''' - -ALL_QUESTION_LIST_QUERY = """ -query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) { - problemsetQuestionList: questionList( - categorySlug: $categorySlug - limit: $limit - skip: $skip - filters: $filters - ) { - questions: data { - frontendQuestionId: questionFrontendId - titleSlug - } - } -} -""" - -def send_query(query, variables): - url = "https://leetcode.com/graphql" - headers = {"Content-Type": "application/json"} - try: - response = requests.post(url, json={"query": query, "variables": variables}, headers=headers) - response.raise_for_status() - except requests.exceptions.RequestException as e: - print(f"Request error: {e}") - return None - - try: - data = response.json() - except requests.exceptions.JSONDecodeError: - print(f"Failed to decode JSON. Status Code: {response.status_code}, Response Text: {response.text}") - return None - - return data \ No newline at end of file diff --git a/server/leaderboard/serializers.py b/server/leaderboard/serializers.py deleted file mode 100644 index c0ef0aa..0000000 --- a/server/leaderboard/serializers.py +++ /dev/null @@ -1,51 +0,0 @@ -from rest_framework import serializers -from django.utils import timezone -from .models import * - -class LeetCodeSerializer(serializers.ModelSerializer): - submissions = serializers.SerializerMethodField() - class Meta: - model = Leetcode - fields = ['username', 'name','leetcode_rank', 'daily_rank','weekly_rank','monthly_rank', 'photo_url', 'submissions'] - - def get_submissions(self, obj): - submission_dict = obj.submission_dict - # Change the values, which are time stamps to readable format - for key, value in submission_dict.items(): - submission_dict[key] = timezone.datetime.fromtimestamp(value).strftime('%Y-%m-%d %H:%M:%S') - return submission_dict - - -class QuestionSerializer(serializers.ModelSerializer): - leetcode_link = serializers.SerializerMethodField() - questionDate = serializers.SerializerMethodField() - status = serializers.SerializerMethodField() - class Meta: - model = Question - fields = ['leetcode_id', 'title', 'leetcode_link','questionDate', 'difficulty', 'status'] - - def get_leetcode_link(self, obj): - return f"https://leetcode.com/problems/{obj.titleSlug}/" - - def get_questionDate(self, obj): - # Keep only the date part - return obj.questionDate.date() - - def get_status(self, obj): - leetcode_acc_user = self.context.get('username') - - if leetcode_acc_user is None: - return "Account ID not provided" - - try: - leetcode_acc_instance = Leetcode.objects.get(username=leetcode_acc_user) - matched_ques_dict = leetcode_acc_instance.matched_ques_dict - # cextract keys and put it in an integer array - matched_ques = [int(key) for key in matched_ques_dict.keys()] - - if obj.leetcode_id in matched_ques: - return "Solved" - else: - return "Not Solved" - except Leetcode.DoesNotExist: - return "Account does not exist" \ No newline at end of file diff --git a/server/leaderboard/tasks.py b/server/leaderboard/tasks.py deleted file mode 100644 index 21856c0..0000000 --- a/server/leaderboard/tasks.py +++ /dev/null @@ -1,285 +0,0 @@ -from celery import shared_task, group -from django.utils import timezone -from datetime import timedelta -from .models import Leetcode, LeaderboardEntry, Question, Leaderboard -from .query_manager import * -import time -from pprint import pprint - -def get_latest_submissions(submissions): - latest_submissions = {} - for submission in submissions: - question_id = int(submission[0]) - timestamp = int(submission[2]) - if question_id not in latest_submissions or timestamp > latest_submissions[question_id]: - latest_submissions[question_id] = timestamp - return latest_submissions - -def match_questions_to_solved(questions, solved_submissions: dict): - matched_ques: dict = {} - for question in questions: - question_id = int(question[0]) - question_timestamp = int(question[2]) - if question_id in solved_submissions.keys(): - solved_timestamp = solved_submissions[question_id] - if solved_timestamp > question_timestamp: - matched_ques[question_id] = solved_timestamp - return matched_ques - -def cal_solved_intervals(questions, solved_dict: dict): - current_time = timezone.now() - one_day_interval = current_time - timedelta(days=1) - one_week_interval = current_time - timedelta(weeks=1) - one_month_interval = current_time - timedelta(days=30) # Approximation for a month - - solved_within_one_day = {} - solved_within_one_week = {} - solved_within_one_month = {} - - for question in questions: - question_id = int(question[0]) - question_timestamp = int(question[2]) - if str(question_id) in solved_dict: - ques_solved_timestamp = solved_dict[str(question_id)] - if ques_solved_timestamp < question_timestamp: - print(f"Question: {question_id} - Solved before creation") - continue # Skip if question was solved before it was created - - if ques_solved_timestamp >= one_day_interval.timestamp(): - solved_within_one_day[question_id] = ques_solved_timestamp - print(f"Question: {question_id} - Solved within one day") - - if ques_solved_timestamp >= one_week_interval.timestamp(): - solved_within_one_week[question_id] = ques_solved_timestamp - print(f"Question: {question_id} - Solved within one week") - - if ques_solved_timestamp >= one_month_interval.timestamp(): - solved_within_one_month[question_id] = ques_solved_timestamp - print(f"Question: {question_id} - Solved within one month") - else: - print(f"Question: {question_id} - Not solved") - - return solved_within_one_day, solved_within_one_week, solved_within_one_month - -def fetch_user_profile(username): - try: - response = send_query(MATCHED_USER_QUERY, {"username": username}) - if response: - return response['data']['matchedUser']['profile'] - except Exception as e: - print(f"Error fetching user profile for {username}: {e}") - return None - -def fetch_submitted_questions(username, limit=500): - try: - return send_query(QUESTIONS_SUBMITTED_QUERY, {"username": username, "limit": limit})['data']['recentAcSubmissionList'] - except Exception as e: - print(f"Error fetching submitted questions for {username}: {e}") - return [] - -def fetch_language_problem_count(username): - try: - return send_query(LANGUAGE_PROBLEM_COUNT_QUERY, {"username": username})['data']['matchedUser']['languageProblemCount'] - except Exception as e: - print(f"Error fetching language problem count for {username}: {e}") - return [] - -def fetch_all_questions(): - try: - query_vars = {"categorySlug": "all-code-essentials", "skip": 0, "limit": 5000, "filters": {}} - return send_query(ALL_QUESTION_LIST_QUERY, query_vars)['data']['problemsetQuestionList']['questions'] - except Exception as e: - print(f"Error fetching all questions: {e}") - return [] - -def get_user_instance(id): - try: - return Leetcode.objects.get(pk=id) - except Exception as e: - print(f"Error getting user instance for id {id}: {e}") - return None - -def update_user_instance(instance: Leetcode, user_data, matched_questions, latest_solved, total_solved, language_problem_count): - try: - if instance and user_data: - instance.name = user_data.get('realName', '') - instance.leetcode_rank = user_data.get('ranking', '') - instance.photo_url = user_data.get('userAvatar', '') - instance.total_solved = sum(question['problemsSolved'] for question in language_problem_count) - instance.matched_ques = len(list(matched_questions.keys())) - instance.submission_dict = latest_solved - instance.matched_ques_dict = matched_questions - instance.total_solved_dict = total_solved - instance.save() - except Exception as e: - print(f"Error updating user instance: {e}") - -def process_submissions(submitted_questions, all_question_list): - try: - titleSlug_to_id = {question['titleSlug']: question['frontendQuestionId'] for question in all_question_list} - return [ - [titleSlug_to_id[question['titleSlug']], question['titleSlug'], question['timestamp']] - for question in submitted_questions if question['titleSlug'] in titleSlug_to_id - ] # [leetcode_id, titleSlug, timestamp] - except Exception as e: - print(f"Error processing submissions: {e}") - return [] - -def generate_leaderboards_entries(): - try: - questions = Question.objects.all() - ques_given = [ - [question.leetcode_id, question.titleSlug, time.mktime(question.questionDate.timetuple())] - for question in questions - ] - user_instances = Leetcode.objects.all() - for user_instance in user_instances: - username = user_instance.username - solved_dict = user_instance.total_solved_dict - solved_within_one_day, solved_within_one_week, solved_within_one_month = cal_solved_intervals(ques_given, solved_dict) - print(f"user: {username} - Solved_dict: {solved_dict}") - daily_entry, _ = LeaderboardEntry.objects.get_or_create(user=user_instance, interval='day') - weekly_entry, _ = LeaderboardEntry.objects.get_or_create(user=user_instance, interval='week') - monthly_entry, _ = LeaderboardEntry.objects.get_or_create(user=user_instance, interval='month') - - daily_entry.questions_solved = len(solved_within_one_day) - weekly_entry.questions_solved = len(solved_within_one_week) - monthly_entry.questions_solved = len(solved_within_one_month) - - daily_entry.earliest_solved_timestamp = max(solved_within_one_day.values(), default=0) - weekly_entry.earliest_solved_timestamp = max(solved_within_one_week.values(), default=0) - monthly_entry.earliest_solved_timestamp = max(solved_within_one_month.values(), default=0) - - daily_entry.save() - weekly_entry.save() - monthly_entry.save() - print("Leaderboard entries generated successfully") - except Exception as e: - print(f"Error generating leaderboard entries: {e}") - -def update_rank(user_list, rank_dict, rank_type): - try: - for idx, user in enumerate(user_list): - user_obj = Leetcode.objects.get(username=user[0]) - '''This code is commented out because it is not necessary to skip users with 0 questions solved.''' - # if user[1] == 0: - # continue - rank_dict[idx+1] = { - "username": user[0], - "photo_url": user_obj.photo_url, - "ques_solv": user[1], - "last_solv": user[2] - } - - try: - user_obj = Leetcode.objects.get(username=user[0]) - if hasattr(user_obj, rank_type): # Check if the attribute exists - setattr(user_obj, rank_type, idx+1) - user_obj.save() - else: - print(f"Attribute {rank_type} does not exist on leetcode_acc objects.") - except Leetcode.DoesNotExist: - print(f"User {user[0]} does not exist.") - except Exception as e: - print(f"Error updating rank: {e}") - -@shared_task(bind=True) -def calculate_leaderboards(self, *args, **kwargs): - try: - generate_leaderboards_entries() - one_day, one_week, one_month = {}, {}, {} - daily, weekly, monthly = [], [], [] - - users = Leetcode.objects.all() - for user in users: - daily_entry = LeaderboardEntry.objects.get(user=user, interval='day') - weekly_entry = LeaderboardEntry.objects.get(user=user, interval='week') - monthly_entry = LeaderboardEntry.objects.get(user=user, interval='month') - daily.append([user.username, daily_entry.questions_solved, daily_entry.earliest_solved_timestamp]) - weekly.append([user.username, weekly_entry.questions_solved, weekly_entry.earliest_solved_timestamp]) - monthly.append([user.username, monthly_entry.questions_solved, monthly_entry.earliest_solved_timestamp]) - - daily.sort(key=lambda x: (-x[1], x[2])) - weekly.sort(key=lambda x: (-x[1], x[2])) - monthly.sort(key=lambda x: (-x[1], x[2])) - # convert timestamp to human readable format - for user in daily: - user[2] = timezone.datetime.fromtimestamp(user[2]).strftime('%Y-%m-%d %H:%M:%S') - for user in weekly: - user[2] = timezone.datetime.fromtimestamp(user[2]).strftime('%Y-%m-%d %H:%M:%S') - for user in monthly: - user[2] = timezone.datetime.fromtimestamp(user[2]).strftime('%Y-%m-%d %H:%M:%S') - - update_rank(daily, one_day, 'daily_rank') - update_rank(weekly, one_week, 'weekly_rank') - update_rank(monthly, one_month, 'monthly_rank') - - # create or update leaderboard (total 3) - Leaderboard.objects.update_or_create( - leaderboard_type='daily', defaults={'leaderboard_data': one_day} - ) - Leaderboard.objects.update_or_create( - leaderboard_type='weekly', defaults={'leaderboard_data': one_week} - ) - Leaderboard.objects.update_or_create( - leaderboard_type='monthly', defaults={'leaderboard_data': one_month} - ) - - print("Leaderboards calculated successfully") - return one_day, one_week, one_month - except Exception as e: - print(f"Error calculating leaderboards: {e}") - return {} - -@shared_task(bind=True) -def get_user_data(self, username, id, *args, **kwargs): - try: - user_profile = fetch_user_profile(username) - if not user_profile: - print(f"No profile data found for user {username}") - return - - submitted_questions = fetch_submitted_questions(username) - language_problem_count = fetch_language_problem_count(username) - all_question_list = fetch_all_questions() - - submissions = process_submissions(submitted_questions, all_question_list) - latest_solved = get_latest_submissions(submissions) - - user_instance = get_user_instance(id) - if not user_instance: - print(f"Failed to get or create user instance for user {username}") - return - - total_solved = user_instance.total_solved_dict - - for ques in latest_solved.keys(): - if ques not in total_solved: - total_solved[ques] = latest_solved[ques] - - questions = Question.objects.all() - ques_given = [ - [question.leetcode_id, question.titleSlug, time.mktime(question.questionDate.timetuple())] - for question in questions - ] - - matched_ques = match_questions_to_solved(ques_given, total_solved) - update_user_instance(user_instance, user_profile, matched_ques, latest_solved, total_solved, language_problem_count) - - print(f"Data for {username} saved successfully") - except Exception as e: - print(f"Error fetching or updating data for user {username}: {e}") - -@shared_task(bind=True) -def refresh_user_data(self): - try: - users = Leetcode.objects.all() - if not users: - return - - user_data_tasks = [get_user_data.s(user.username, user.id) for user in users] - task_chain = group(user_data_tasks) | calculate_leaderboards.s() - task_chain.apply_async() - print("Data refreshed and leaderboard initiated successfully") - except Exception as e: - print(f"Error refreshing user data: {e}") diff --git a/server/leaderboard/tests.py b/server/leaderboard/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/server/leaderboard/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/server/leaderboard/urls.py b/server/leaderboard/urls.py deleted file mode 100644 index b12ae1f..0000000 --- a/server/leaderboard/urls.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.urls import path -from . import views - -urlpatterns = [ - path('refresh_data/', views.DebugRefreshUserData.as_view(), name='refresh_data'), - - # POST - path('register/', views.Register.as_view(), name='register'), - - # GET - path('user/profile/', views.Profile.as_view(), name='profile'), - path('questions/today/', views.GetQuestionsForTheDay.as_view(), name='today_questions'), - path('questions/all/', views.GetAllQuestions.as_view(), name='all_questions'), - path('daily/', views.DailyLeaderboard.as_view(), name='daily_leaderboard'), - path('weekly/', views.WeeklyLeaderboard.as_view(), name='weekly_leaderboard'), - path('monthly/', views.MonthlyLeaderboard.as_view(), name='monthly_leaderboard'), -] diff --git a/server/leaderboard/views.py b/server/leaderboard/views.py deleted file mode 100644 index 8d1fb06..0000000 --- a/server/leaderboard/views.py +++ /dev/null @@ -1,106 +0,0 @@ -from django.shortcuts import get_object_or_404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated - -from rest_framework import status -from django.utils import timezone -from .models import Leetcode, Question, Leaderboard -from ccs_auth.models import CUser -from .tasks import get_user_data, refresh_user_data -from .serializers import LeetCodeSerializer, QuestionSerializer - -def get_today_questions(username): - today = timezone.now() - start_of_today = today.replace(hour=0, minute=0, second=0, microsecond=0) - end_of_today = today.replace(hour=23, minute=59, second=59, microsecond=999999) - questions = Question.objects.filter(questionDate__range=(start_of_today, end_of_today)) - context = {'username': username} - serializer = QuestionSerializer(questions, many=True, context=context) - return serializer.data - - -class Register(APIView): - permission_classes = [IsAuthenticated] - def post(self, request,*args, **kwargs): - user = request.user - username = request.data.get('username') - if not username: - return Response({"error": "Username is required"}, status=status.HTTP_400_BAD_REQUEST) - acc = Leetcode.objects.create(username=username, user=user) - print(f"User {user} registered with Leetcode username {username}") - if not acc: - return Response({"error": "User registration failed"}, status=status.HTTP_400_BAD_REQUEST) - get_user_data.delay(username, acc.pk) - return Response({"message": "User registered successfully"}, status=status.HTTP_201_CREATED) - -class Profile(APIView): - permission_classes = [IsAuthenticated] - def get(self, request, *args, **kwargs): - user = request.user - username = user.leetcode.username - if not username: - return Response({"error": "Username is required"}, status=status.HTTP_400_BAD_REQUEST) - - try: - account = Leetcode.objects.get(username=username) - except Leetcode.DoesNotExist: - return Response({"error": "User does not exist"}, status=status.HTTP_404_NOT_FOUND) - - serializer = LeetCodeSerializer(account) - return Response(serializer.data, status=status.HTTP_200_OK) - -class GetQuestionsForTheDay(APIView): - permission_classes = [IsAuthenticated] - def get(self, request, *args, **kwargs): - try: - user = request.user - username = user.leetcode.username - if not username: - return Response({"error": "Username is required"}, status=status.HTTP_400_BAD_REQUEST) - questions_data = get_today_questions(username) - return Response(questions_data, status=status.HTTP_200_OK) - except Question.DoesNotExist: - return Response({"error": "Questions not found"}, status=status.HTTP_404_NOT_FOUND) - - -class GetAllQuestions(APIView): - def get(self, request, *args, **kwargs): - try: - questions = Question.objects.all() - serializer = QuestionSerializer(questions, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - except Question.DoesNotExist: - return Response({"error": "Questions not found"}, status=status.HTTP_404_NOT_FOUND) - -class DailyLeaderboard(APIView): - def get(self, request, *args, **kwargs): - try: - one_day = Leaderboard.objects.get(leaderboard_type='daily').leaderboard_data - except Leaderboard.DoesNotExist: - return Response({"error": "Daily leaderboard not found"}, status=status.HTTP_404_NOT_FOUND) - - return Response(one_day, status=status.HTTP_200_OK) - -class WeeklyLeaderboard(APIView): - def get(self, request, *args, **kwargs): - try: - one_week = Leaderboard.objects.get(leaderboard_type='weekly').leaderboard_data - except Leaderboard.DoesNotExist: - return Response({"error": "Weekly leaderboard not found"}, status=status.HTTP_404_NOT_FOUND) - - return Response(one_week, status=status.HTTP_200_OK) - -class MonthlyLeaderboard(APIView): - def get(self, request, *args, **kwargs): - try: - one_month = Leaderboard.objects.get(leaderboard_type='monthly').leaderboard_data - except Leaderboard.DoesNotExist: - return Response({"error": "Monthly leaderboard not found"}, status=status.HTTP_404_NOT_FOUND) - - return Response(one_month, status=status.HTTP_200_OK) - -class DebugRefreshUserData(APIView): - def get(self, request, *args, **kwargs): - refresh_user_data.delay() - return Response({"message": "Data refresh initiated"}, status=status.HTTP_200_OK) diff --git a/server/manage.py b/server/manage.py deleted file mode 100644 index 3740dad..0000000 --- a/server/manage.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" -import os -import sys -from subprocess import Popen - -def start_celery(): - Popen(['celery', '-A', 'app.celery', 'worker','--pool=solo', '-l', 'info']) - Popen(['celery', '-A', 'app.celery', 'beat', '-l', 'info']) - -def main(): - """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') - try: - from django.core.management import execute_from_command_line - if 'runserver' in sys.argv: - start_celery() - - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) - - -if __name__ == '__main__': - main() diff --git a/server/requirements.txt b/server/requirements.txt deleted file mode 100644 index 7a363ca..0000000 --- a/server/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -Django -python-dotenv -psycopg2 -djangorestframework -requests -celery -redis -django-celery-beat -django-celery-results -django-cors-headers \ No newline at end of file diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..2e53024 --- /dev/null +++ b/src/App.css @@ -0,0 +1,1168 @@ +@import '@radix-ui/colors/black-alpha.css'; +@import '@radix-ui/colors/mauve.css'; +@import '@radix-ui/colors/violet.css'; + +@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); + +:root { + --NAV-COLOR: rgb(212, 212, 212); + --NAV-BORDER-COLOR: #525151; + /* --NAV-BGCOLOR: rgb(51, 55, 62); */ + --NAV-BGCOLOR: rgb(34, 36, 41); + --NAV-BORDER: 2px solid var(--NAV-BORDER-COLOR); + --CONTENT-H2-COLOR: rgb(212, 212, 212); + --CONTENT-COLOR: rgb(188, 197, 206); + --LINK-COLOR: white; + --LINK-HOVER-BGCOLOR: rgba(62, 216, 219, 0.866); + --LINK-FOCUS-BGCOLOR: rgba(62, 216, 219, 0.866); + --LINK-ACTIVE-COLOR: rgb(82, 81, 81); + --cont-color: rgb(18, 17, 17); /* Dark background for containers */ + --ac-color: rgba(62, 216, 219, 0.866); /* Accent color for labels and headings */ + --text-color: #ffffff; /* White text color */ + --input-focus-color: rgb(77, 77, 77); +} +*{ + margin:0; + padding: 0; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} +body { + background-color: black; +} + +/* NAVBAR ***************************** */ +.NavBar { + border-right: var(--NAV-BORDER); + padding: 1rem; + background-color: var(--NAV-BGCOLOR); + width: 18rem; + min-width: 14rem; + box-sizing: border-box; + overflow-y: auto; + position: fixed; + top: 0; + bottom: 0; + transition: width 0.3s ease, height 0.3s ease; + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100vh; + +} + +.NavBar > h2 { + font-weight: 500; + color: var(--NAV-COLOR); +} + +.NavBar ul { + list-style-type: none; + padding: 0; + margin-top: 1rem; + flex-grow: 1; + display:flex; + flex-direction: column; +} +.NavBar .logout-button-container { + + display: flex; + justify-content:flex-end; +} +.NavBar .logout-button-container .logout-button { + background-color: rgba(62, 216, 219, 0.866); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 0.25rem; + margin-right: 1rem; /* Adjust the right margin as needed */ + cursor: pointer; + font-size: 1rem; +} + +.NavBar .logout-button-container .logout-button:hover { + background-color: rgba(62, 216, 219, 0.866); +} + +.NavBar .logout-button-container .logout-button:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(62, 216, 219, 0.866); +} +.NavBar li { + padding-bottom: 0.3rem; + margin-bottom: 1rem; + display: flex; + align-items: center; +} + +.NavBar li a, +.NavBar li span { + text-decoration: none; + color: var(--NAV-COLOR); + padding: 0.8rem; + + flex-grow: 1; + font-size: 1.4rem; +} + +.NavBar li a:hover { + background-color: var(--LINK-HOVER-BGCOLOR); + color: rgb(253, 254, 254); + cursor: pointer; + border-radius: 7px; +} + +.NavBar li:active, +.NavBar li a:active, +.NavBar li span:active { + color: var(--LINK-ACTIVE-COLOR); +} + +.NavBar li:focus, +.NavBar li a:focus, +.NavBar li span:focus { + background-color: var(--LINK-HOVER-BGCOLOR); + color: rgb(253, 254, 254); + outline-offset: 4px; + border-radius: 7px; +} + +.nav-icon { + margin-left: 0.5rem; + margin-right: 1rem; + font-size: 1.2rem; +} + +.ccsLogo { + height: 7.5rem; + margin-bottom: 2rem; + display: block; + margin-left: auto; /* Center horizontally */ + margin-right: auto; /* Center horizontally */ +} +@media (max-width: 1235px) { + .NavBar .logout-button-container .logout-button { + font-size: 0.875rem; /* Reduce font size for smaller screens */ + padding: 0.2rem 0.5rem; /* Adjust padding for smaller screens */ + } +} + +@media (max-width: 768px) { + .NavBar .logout-button-container .logout-button { + font-size: 0.75rem; /* Further reduce font size for even smaller screens */ + padding: 0.15rem 0.4rem; /* Further adjust padding */ + } +} +/* Scrollbar styles */ +.NavBar::-webkit-scrollbar, +body::-webkit-scrollbar { + width: 8px; +} + +.NavBar::-webkit-scrollbar-track, +body::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.1); +} + +.NavBar::-webkit-scrollbar-thumb, +body::-webkit-scrollbar-thumb { + background-color: rgb(82, 81, 81); + border-radius: 10px; +} + +.NavBar::-webkit-scrollbar-thumb:hover, +body::-webkit-scrollbar-thumb:hover { + cursor: pointer; +} + +.divider { + font-weight: 600; + pointer-events: none; + color: var(--CONTENT-H2-COLOR); +} + +/* APP-CONTAINER ****************************/ +.app-container { + display: flex; + flex-grow: 1; + max-width: 190rem; + +} + +.content { + flex-grow: 1; + text-align: center; + padding: 2rem 9rem; + /* margin-top: 2rem; */ + margin-left: 13rem; + + color: var(--CONTENT-COLOR); + overflow-y: auto; +} + +.content h2 { + color: rgb(33, 35, 42); + font-weight: 700; + font-family: 'Roboto', sans-serif; +} + +.content p { + font-family: 'Open Sans', sans-serif; + line-height: 1.8; + margin-bottom: 1rem; +} + +/* Profile ****************/ +.profile-container { + display: flex; + justify-content: space-between; /* Space between containers */ + background-color: #282c34; + color: var(--CONTENT-COLOR); + padding: 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + /* Adjusted width to fit both containers */ + margin: 0 auto; + margin-top: 50px; /* Add margin-top for centering */ + position: relative; /* Ensure it's centered vertically */ +} + +.detail-container { + flex: 1; /* Flex to adjust with the container */ + text-align: center; + padding-right: 1rem; /* Padding to add space between containers */ +} + +.detail-container img { + border-radius: 50%; + width: 150px; + height: 150px; + object-fit: cover; + margin-bottom: 20px; + border: 3px solid var(--CONTENT-H2-COLOR); /* Add border */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); /* Add shadow */ +} + +.detail-container h1 { + font-size: 2.5em; /* Increase font size */ + color: white; /* Change name color to white */ + margin-bottom: 10px; +} + +.detail-container p { + font-size: 1.2em; + color: var(--CONTENT-COLOR); + margin: 5px 0; +} + +.detail-container p span { + font-weight: bold; + color: white; /* Ensure high contrast */ + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} + +/* Loading and Error Styles */ +.loading, +.error { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + font-size: 1.5em; + color: rgb(33, 35, 42); +} +/* Rank Slider ****************/ +.rank-slider button { + background: none; + border: none; + padding: 0; + cursor: pointer; + font: inherit; + outline: inherit; +} +.rank-slider { + display: flex; + justify-content: center; + margin-bottom: 20px; +} + +.rank-slider .tab { + text-decoration: none; /* Remove default underline */ + color: var(--CONTENT-COLOR); + padding: 10px 20px; + margin: 0 5px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 1rem; + transition: text-decoration-color 0.3s; /* Add transition for underline color */ +} + +.rank-slider .tab.active { + text-decoration: underline; /* Underline when active */ + text-decoration-color: rgba(62, 216, 219, 0.866); /* Active underline color */ + text-underline-offset: 2px; /* Optional: adjust underline position */ +} +/* Responsive Design for Rank Slider */ +@media (max-width: 768px) { + .rank-slider .tab { + padding: 5px 10px; + font-size: 0.875rem; + } +} +/* Adding responsive design */ +@media (max-width: 768px) { + .profile-container { + padding: 1rem; + } + + .profile-container h1 { + font-size: 1.5em; + } + + .profile-container p { + font-size: 1em; + } +} + +.question-container { + flex: 2; /* Flex to adjust with the container */ +} + +.question-container h2 { + color: white; /* Change heading color to white */ + text-align: center; + margin-bottom: 0.5rem; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} +/* +.questions-table { + width: 100%; + margin-top: 20px; + border-collapse: collapse; + background-color: var(--NAV-BGCOLOR); +} + +.questions-table th, +.questions-table td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.questions-table th { + background-color: var(--NAV-BGCOLOR); + color: white; +} + +.questions-table tr:hover { + background-color: var(--NAV-COLOR); + color: black; +} + +.questions-table a { + color: rgba(62, 216, 219, 0.866); + text-decoration: none; +} + +.questions-table a:hover { + text-decoration: underline; + color: rgb(57, 185, 255); +} + */ +/* LEADERBOARD ****************/ +.leaderboard-container { + background-color: var(--NAV-BGCOLOR); + border-radius: 1rem; /* 15px in rem */ + padding: 2rem 4rem 4rem; /* 20px in rem */ + margin: 0 1rem; /* 20px in rem */ +} +.leaderboard-container .leaderboard-title-heading { + color: white; + text-align: left; + margin-bottom: 0.5rem; + font-family: "Poppins", sans-serif; /* Ensure the font is applied */ + font-weight: 400; + font-style: normal; +} +.leaderboard-title { + color: white; + text-align: left; + margin-bottom: 1rem; +} +.questions-table, +.leaderboard-table { + width: 100%; + border-collapse: collapse; + background-color: var(--NAV-BGCOLOR); + color: var(--CONTENT-COLOR); + border: 2px solid rgba(62, 216, 219, 0.866); + border-radius: 1rem; + overflow: hidden; /* Ensure the border-radius applies to the entire table */ + +} + +.questions-table th, +.questions-table td, +.leaderboard-table th, +.leaderboard-table td { + padding: 0.9375rem; /* 15px in rem */ + text-align: centre; + border-top: 0.0625rem solid rgba(62, 216, 219, 0.866); /* 1px in rem */ +} +.questions-table th:first-child, +.questions-table td:first-child{ + text-align: left; +} +.questions-table a { + color: white; /* Change link color to white */ + text-decoration: none; /* Remove underline if desired */ +} + +.questions-table a:hover { + text-decoration: underline; /* Optional: Add underline on hover */ + color: rgba(62, 216, 219, 0.866); /* Optional: Change color on hover */ +} +.questions-table th, +.leaderboard-table th { + background-color: var(--NAV-BGCOLOR); + color: white; + text-align: center; +} + +.leaderboard-row { + margin: 0.3125rem; /* 5px in rem */ +} + +.leaderboard-row td { + margin: 0.125rem; /* 2px in rem */ + background-color: var(--NAV-BGCOLOR); + text-align: center; +} + +.leaderboard-row img { + border-radius: 50%; + width: 3.125rem; /* 50px in rem */ + height: 3.125rem; /* 50px in rem */ + object-fit: cover; +} +.leaderboard-table th:first-child, +.leaderboard-table td:first-child { + border-radius: 1rem; +} + +.leaderboard-table th:last-child, +.leaderboard-table td:last-child { + border-radius: 1rem; +} + +.leaderboard-table tr:last-child td:first-child { + border-radius: 1rem; +} + +.leaderboard-table tr:last-child td:last-child { + border-radius: 1rem; +} +.leaderboard .badge { + width: auto; + height: 30px; /* Adjust this value as needed */ + object-fit: contain; + margin-right: 10px; +} + +.leaderboard .badge { + width: auto; + height: 40px; /* Adjust as needed */ + object-fit: contain; + margin-right: 10px; + vertical-align: middle; + border-radius: 0 !important; + border: none !important; + box-shadow: none !important; +} + +/* Responsive Design for Leaderboard */ +@media (max-width: 768px) { + .profile-container { + padding: 1rem; /* Equal padding on all sides */ + margin:0; + } + + .content { + padding: 0.5rem; /* Equal padding on all sides */ + margin: 0; /* Remove any extra margin */ + } + .question-container, + .leaderboard-container { + padding: 1rem; /* Adjust padding for smaller screens */ + margin: 0.5rem; /* Adjust margin for smaller screens */ + + } + .question-container h2, + .leaderboard-title-heading, + .leaderboard-title { + font-size: 0.875rem; /* Adjust font size for smaller screens */ + text-align: center !important; /* Center-align text for smaller screens */ + } + .questions-table th, + .questions-table td, + .leaderboard-table th, + .leaderboard-table td { + padding: 0.5rem; /* Reduce padding for smaller screens */ + font-size: 0.75rem; /* Reduce font size for smaller screens */ + text-align: center; + } + + .leaderboard-row img { + width: 2rem; /* Reduce image size for smaller screens */ + height: 2rem; /* Reduce image size for smaller screens */ + } + .questions-table, + .leaderboard-table { + width: 100%; /* Ensure the table width fits within the container */ + table-layout: fixed; /* Ensure consistent table cell widths */ + } + .questions-table th, + .questions-table td, + .leaderboard-table th, + .leaderboard-table td { + white-space: normal; /* Allow text to wrap within cells */ + word-wrap: break-word; /* Ensure long words are wrapped */ + } +} + +/* Responsive Navbar for Smaller Screens */ +@media (max-width: 1235px) { + .NavBar { + width: 100%; + height: 7rem; + position: fixed; + top: 0; + left: 0; + background-color: var(--NAV-BGCOLOR); + padding: 0; + overflow: hidden; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; + z-index: 1000; /* Ensure it is above other content */ + border-right: none; /* Remove right border */ + border-bottom: var(--NAV-BORDER); /* Add bottom border */ + } + + .NavBar ul { + display: flex; + flex-direction: row; + align-items: right; + width: 70%; + justify-content: space-around; + } + + .NavBar li { + margin: 0; + padding: 0; + } + + .NavBar li a, + .NavBar li span { + padding: 0.2rem; + padding-bottom: 0; + } + + .nav-icon { + font-size: 1.5rem; /* Increase icon size */ + margin: 0; + } + .ccsLogo{ + margin-top: 1rem; + height: 4.5rem; + margin-left: 0.5rem; + } + .NavBar > h2, + .NavBar li a span, + .NavBar li span span { + display: none; /* Hide text */ + } + .NavBar .divider{ + display: none; + } + .app-container { + margin-top: 7rem; /* Add top margin to avoid overlap with the navbar */ + margin-left: 0; /* Reset left margin */ + } + + .content { + margin-top: 0rem; + margin-left: 1rem; + margin-right: 1rem; + padding: 1rem; + } +} + +@media (max-width: 768px) { + .profile-container { + flex-direction: column; + align-items: center; + } + + .detail-container { + padding-right: 0; + margin-bottom: 1rem; /* Add space between the containers */ + } + + .question-container { + width: 100%; /* Ensure the question container takes full width */ + } + +} +@media (max-width: 425px){ + .leaderboard-table th:last-child, + .leaderboard-table td:last-child { + display: none; /* Hide the "Questions Solved" column */ + } +} + +/* UsernameEntry styles *****************************************/ +body.username-entry-body { + background-color: black; /* Black background for the body */ + color: var(--text-color); /* White text color */ + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + padding: 0; + margin: 0; +} +.container { + background-color: var(--cont-color); + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 30px; + max-width: 1000px; + width: 100%; + text-align: center; + display: flex; + justify-content: space-evenly; + align-items: stretch; + flex-wrap: wrap; +} + +/* Left Column Styles */ +.left { + display: flex; + flex-direction: column; + width: 45%; + + border-radius: 8px 0 0 8px; + align-items: center; + gap: 30px; + background: var(--cont-color); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 12px; +} + +.left-top { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + margin-top: 30px; + gap: 15px; + text-align: center; +} + +.left-top h1 { + font-size: 32px; + color: var(--ac-color); + margin: 0; + font-weight: 700; +} + +.left-top h2 { + font-size: 16px; + color: white; + font-weight: 500; +} + +.left-bottom { + display: flex; + flex-direction: column; + align-items: center; + + width: 100%; +} + +.left-bottom h4 { + font-size: 20px; + color: var(--ac-color); + + font-weight: 600; +} + +.logo img { + width: 5rem; + border-radius: 50%; +} + +.left-bottom .content { + display: flex; + align-items: center; + margin: 0.5rem; + padding: 0.4rem; +} + +.profile { + width: 60px; + height: 60px; + margin-right: 10px; /* Adjust margin as needed */ +} + +.content-left { + flex: 1; /* Take up remaining space */ + +} +@media (max-width: 1024px) { + .outer-container{ + width: 90%; + } +} +/* Adjustments for smaller screens */ +@media (max-width: 768px) { + .outer-container{ + width: 82%; + margin-top: 20rem; + } + .left { + flex-direction: column; + align-items: center; + text-align: center; + } + + .content { + flex-direction: column; + align-items: center; + } + + .profile { + margin-bottom: 10px; /* Adjust margin for spacing */ + } +} +/* Right Column Styles */ +.right { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + margin-left: 8px; + width: 50%; + margin-bottom: 8px; +} + +/* Form Styles */ +.user-form { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + height: 100%; + justify-content: space-between; +} + +.input-group-row { + display: flex; + gap: 15px; +} + +.input-group { + position: relative; + margin-top: 10px; + height: 56px; +} + +.input-group input, +.input-group select { + width: 100%; + height: 100%; + padding: 0 10px; + border: 1px solid var(--ac-color); + border-radius: 4px; + font-size: 14px; + line-height: 56px; + background-color: var(--cont-color); + color: var(--text-color); /* White text color */ +} +.input-group #roll-number, +.input-group #email{ + color: rgb(179, 179, 179); +} + +.input-group label { + position: absolute; + top: 0; + left: 0; + font-size: 12px; + color: var(--ac-color); + transform: translateY(-50%) scale(0.75) translateX(-5%); + background: var(--cont-color); + pointer-events: none; + transition: none; +} + +.input-group input:focus ~ label, +.input-group input:not(:placeholder-shown) ~ label, +.input-group select:focus ~ label, +.input-group select:not([value=""]) ~ label { + top: 0; +} + +.input-group select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%235f6368' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 40px; +} +.input-group input:focus { + background-color: var(--cont-color); /* Dark background */ + color: var(--text-color); /* White text */ + border-color: var(--ac-color); /* Accent color border */ +} + +/* Optional: Style for when the input is not empty */ +.input-group input:not(:placeholder-shown) { + background-color: var(--cont-color); /* Dark background */ + color: var(--text-color); /* White text */ +} +.input-group select option[disabled] { + display: none; +} + +.input-group select:invalid { + color: #5f6368; +} + +/* Policy Styles */ +.policy { + display: flex; + align-items: center; +} + +.policy label { + font-size: 14px; + color: #5f6368; + display: flex; + align-items: center; +} + +.policy input[type="checkbox"] { + width: auto; + margin-right: 10px; +} + +/* Button Styles */ +button { + background-color: var(--ac-color); + color: #ffffff; + padding: 10px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} + +button:hover { + background-color: #139ad6; +} + +/* Media Queries */ +@media (max-width: 768px) { + + .container { + margin-top: 6rem; + flex-direction: column; + } + + .left, + .right { + width: 100%; + margin: 0; + } +} + +@media (max-width: 576px) { + .container { + box-shadow: none; + } + + .left { + + box-shadow: none; + } + + .left-top h1 { + font-size: 24px; + } + + .left-top h2 { + font-size: 14px; + } + + .left-bottom h4 { + font-size: 18px; + } + + .content p { + font-size: 14px; + } + + .input-group label { + top: 0; + } + + .input-group { + height: 45px; + } + + .input-group input, + .input-group select { + width: 100%; + height: 40px; + padding: 0 10px; + border: 1px solid #39b9bc; + border-radius: 4px; + font-size: 14px; + line-height: 1.5; + background-color: var(--cont-color); + } + + .policy label { + font-size: 12px; + } + + button { + padding: 12px; + font-size: 16px; + } + + .input-group label { + font-size: 12px; + } + + .input-group input:focus ~ label, + .input-group input:not(:placeholder-shown) ~ label, + .input-group select:focus ~ label, + .input-group select:not([value=""]):not([value="Select your branch"]) ~ label { + font-size: 10px; + } +} + +@media (max-width: 480px) { + .input-group-row { + flex-direction: column; + } + + .input-group-row .input-group { + width: 100%; + } +} +@media (max-width: 600px) { + body{ + background-color: rgb(18, 17, 17); + } +} +@media (max-width: 445px) { + + .container { + margin-top: 0; + flex-direction: column; + } +} + + +/* CONFIRMATION POPUP */ +.AlertDialogOverlay { + background-color: rgba(0, 0, 0, 0.455); + position: fixed; + inset: 0; + animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); +} + +.AlertDialogContent { + background-color: rgb(51, 55, 62); + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + color: white; + border-radius: 6px; + box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 90vw; + max-width: 500px; + max-height: 85vh; + padding: 25px; + animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); + text-align: center; +} + +.AlertDialogContent:focus { + outline: none; +} + +.AlertDialogTitle { + margin: 0; + color: white; + font-size: 17px; + font-weight: 500; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} + +.AlertDialogDescription { + margin-top: 10px; + margin-bottom: 20px; + color: white; + font-size: 15px; + line-height: 1.5; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} + +.Button { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + padding: 0 15px; + font-size: 15px; + line-height: 1; + font-weight: 500; + height: 35px; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} + +.Button.violet { + background-color: rgba(62, 216, 219, 0.866); + color: white; +} + +.Button.violet:hover { + background-color: rgba(55, 191, 194, 0.866); +} + +.Button.violet:focus { + box-shadow: 0 0 0 2px black; +} + +.Button.mauve { + background-color: red; + color: white; +} + +.Button.mauve:hover { + background-color: rgb(206, 5, 5); +} + +.Button.mauve:focus { + box-shadow: 0 0 0 2px black; +} + +.avatar-container { + display: flex; + justify-content: center; + margin-bottom: 15px; +} + +.avatar { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; +} + +.button-container { + display: flex; + justify-content: center; + gap: 25px; + margin-top: 20px; +} + +@keyframes overlayShow { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes contentShow { + from { + opacity: 0; + transform: translate(-50%, -48%) scale(0.96); + } + to { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } +} + + +/*LOGOUT*/ + + +/*logout btn*/ +/* Add this to your CSS file (e.g., App.css) */ + +.NavBar .logout-button-container .logout-button { + display: flex; + align-items: center; + background-color: rgba(62, 216, 219, 0.866); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 0.25rem; + cursor: pointer; + font-size: 1rem; +} + +.logout-icon { + font-size: 1.5rem; + margin-right: 0.5rem; +} + +.logout-text { + display: inline; +} + +@media (max-width: 1235px) { + .logout-text { + display: none; + } + .NavBar .logout-button-container .logout-button { + background-color: transparent; + margin-top: 0.7rem; + } + .logout-button { + padding: 0.5rem; + background-color: transparent; + } + + .logout-icon { + font-size: 1.5rem; + } +} diff --git a/client/src/App.js b/src/App.js similarity index 69% rename from client/src/App.js rename to src/App.js index 15c7206..d0b2fd7 100644 --- a/client/src/App.js +++ b/src/App.js @@ -8,22 +8,27 @@ import Monthly from './Components/Monthly'; import Login from './Components/Login'; import AuthVerify from './Components/AuthVerify'; import UsernameEntry from './Components/UsernameEntry'; -import './App.css'; +import './Common.css'; +import './AppNew.css'; function App() { - const [isAuthenticated, setIsAuthenticated] = useState(false); const [isNewUser, setIsNewUser] = useState(false); + const [isAuthenticated, setIsAuthenticated] = useState(false); - const handleLogin = () => { - if(localStorage.getItem('token')) - setIsAuthenticated(true); + const handleLogin = async () => { + if (localStorage.getItem('token')) { + setIsAuthenticated(true); + } else { + setIsAuthenticated(false); + } }; useEffect(() => { handleLogin(); - console.log("isAuthenticated:", isAuthenticated); - }, [] -) + }, []); + + // console.log('Is Authenticated:', isAuthenticated); + return (
@@ -38,14 +43,17 @@ function App() { } /> } /> } /> + + } /> + {isNewUser && } />}
) : ( } /> - } /> - {isNewUser && } />} + } /> + {isNewUser && } />} } /> )} @@ -54,4 +62,4 @@ function App() { ); } -export default App; +export default App; \ No newline at end of file diff --git a/src/AppNew.css b/src/AppNew.css new file mode 100644 index 0000000..4d72e42 --- /dev/null +++ b/src/AppNew.css @@ -0,0 +1,50 @@ +*{ + margin:0; + padding: 0; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} +h1, h2{ + font-family: "Poppins", sans-serif !important; +} +.app-container { + display: flex; + flex-grow: 1; + max-width: 190rem; + } + + .content { + flex-grow: 1; + text-align: center; + padding: 2rem 9rem; + margin-left: 13rem; + color: var(--CONTENT-COLOR); + overflow-y: auto; + } + + .content h2 { + color: rgb(33, 35, 42); + font-weight: 700; + font-family: 'Roboto', sans-serif; + } + + .content p { + font-family: 'Open Sans', sans-serif; + line-height: 1.8; + margin-bottom: 1rem; + } + + @media (max-width: 1235px) { + .content { + padding: 1rem; + margin-left: 0; + margin-top: 7rem; + } + } + + @media (max-width: 768px) { + .content { + margin-top: 7rem; + } + } \ No newline at end of file diff --git a/src/Common.css b/src/Common.css new file mode 100644 index 0000000..76a9329 --- /dev/null +++ b/src/Common.css @@ -0,0 +1,33 @@ +@import '@radix-ui/colors/black-alpha.css'; +@import '@radix-ui/colors/mauve.css'; +@import '@radix-ui/colors/violet.css'; + +@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); + +:root { + --NAV-COLOR: rgb(212, 212, 212); + --NAV-BORDER-COLOR: #525151; + /* --NAV-BGCOLOR: rgb(51, 55, 62); */ + --NAV-BGCOLOR: rgb(34, 36, 41); + --NAV-BORDER: 2px solid var(--NAV-BORDER-COLOR); + --CONTENT-H2-COLOR: rgb(212, 212, 212); + --CONTENT-COLOR: rgb(188, 197, 206); + --LINK-COLOR: white; + --LINK-HOVER-BGCOLOR: rgba(62, 216, 219, 0.866); + --LINK-FOCUS-BGCOLOR: rgba(62, 216, 219, 0.866); + --LINK-ACTIVE-COLOR: rgb(82, 81, 81); + --cont-color: rgb(18, 17, 17); /* Dark background for containers */ + --ac-color: rgba(62, 216, 219, 0.866); /* Accent color for labels and headings */ + --text-color: #ffffff; /* White text color */ + --input-focus-color: rgb(77, 77, 77); +} +*{ + margin:0; + padding: 0; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} +body { + background-color: black; +} \ No newline at end of file diff --git a/src/Components/AuthVerify.js b/src/Components/AuthVerify.js new file mode 100644 index 0000000..8d57dfb --- /dev/null +++ b/src/Components/AuthVerify.js @@ -0,0 +1,75 @@ +import React, { useEffect } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import SERVER_URL from "../config.js"; + +const API_URL = `${SERVER_URL}api/auth`; + +const AuthVerify = ({ onVerify, setIsNewUser, setIsAuthenticated }) => { + const navigate = useNavigate(); + const location = useLocation(); + + useEffect(() => { + const verifyUser = async () => { + const query = new URLSearchParams(location.search); + const jwtToken = query.get('token'); + + if (jwtToken) { + // console.log('JWT Token:', jwtToken); + + const requestBody = { token: jwtToken }; + + try { + const response = await fetch(`${API_URL}/login/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(requestBody), + }); + + const data = await response.json(); + const status = response.status; + + /* console.log('Response Status:', status); + console.log('Response Data:', data); */ + + if (data.leetcode === false) { + setIsNewUser(true); + onVerify(); + const userData = { + rollNo: data.user.roll_no, + email: data.user.email, + branch: data.user.branch, + fullName: `${data.user.first_name} ${data.user.last_name}` + }; + setIsAuthenticated(true); // Set isAuthenticated to true + navigate('/username', { state: { jwtToken, userData, token: data.token } }); + // console.log('Navigated to /username'); + } else if (status === 200 && data.token) { + localStorage.setItem('token', data.token); + // console.log('Token stored in localStorage:', localStorage.getItem('token')); + onVerify(); + setIsAuthenticated(true); // Set isAuthenticated to true + navigate('/profile'); + // console.log('Navigated to /profile'); + } else { + console.error(`Unexpected response: ${JSON.stringify(data)}`); + navigate('/login'); + // console.log('Navigated to /login1'); + } + } catch (error) { + console.error('Error checking user:', error); + navigate('/login'); + // console.log('Navigated to /login2'); + } + } else { + navigate('/login'); + // console.log('Navigated to /login3'); + } + }; + + verifyUser(); + }, [navigate, location, onVerify, setIsNewUser, setIsAuthenticated]); + + return
Loading...
; +}; + +export default AuthVerify; diff --git a/src/Components/Confirmation.css b/src/Components/Confirmation.css new file mode 100644 index 0000000..680a38e --- /dev/null +++ b/src/Components/Confirmation.css @@ -0,0 +1,137 @@ +@import '@radix-ui/colors/black-alpha.css'; +@import '@radix-ui/colors/mauve.css'; +@import '@radix-ui/colors/violet.css'; +@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); + +.AlertDialogOverlay { + background-color: rgba(0, 0, 0, 0.455); + position: fixed; + inset: 0; + animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); +} + +.AlertDialogContent { + background-color: rgb(51, 55, 62); + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + color: white; + border-radius: 6px; + box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 90vw; + max-width: 500px; + max-height: 85vh; + padding: 25px; + animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); + text-align: center; +} + +.AlertDialogContent:focus { + outline: none; +} + +.AlertDialogTitle { + margin: 0; + color: white; + font-size: 17px; + font-weight: 500; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} + +.AlertDialogDescription { + margin-top: 10px; + margin-bottom: 20px; + color: white; + font-size: 15px; + line-height: 1.5; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} + +.Button { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + padding: 0 15px; + font-size: 15px; + line-height: 1; + font-weight: 500; + height: 35px; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} + +.Button.violet { + background-color: rgba(62, 216, 219, 0.866); + color: white; +} + +.Button.violet:hover { + background-color: rgba(55, 191, 194, 0.866); +} + +.Button.violet:focus { + box-shadow: 0 0 0 2px black; +} + +.Button.mauve { + background-color: red; + color: white; +} + +.Button.mauve:hover { + background-color: rgb(206, 5, 5); +} + +.Button.mauve:focus { + box-shadow: 0 0 0 2px black; +} + +.avatar-container { + display: flex; + justify-content: center; + margin-bottom: 15px; +} + +.avatar { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; +} + +.button-container { + display: flex; + justify-content: center; + gap: 25px; + margin-top: 20px; +} + +@keyframes overlayShow { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes contentShow { + from { + opacity: 0; + transform: translate(-50%, -48%) scale(0.96); + } + to { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } +} \ No newline at end of file diff --git a/src/Components/Confirmation.js b/src/Components/Confirmation.js new file mode 100644 index 0000000..c7bc6a0 --- /dev/null +++ b/src/Components/Confirmation.js @@ -0,0 +1,64 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import * as AlertDialog from '@radix-ui/react-alert-dialog'; + +const Confirmation = ({ response, username, onConfirm, onCancel }) => { + let message1 = ''; + + let avatar = null; + let realName = ''; + if (response.ranking !== undefined) { + message1 = "Is this your LeetCode account?"; + realName = response.realName; + avatar = response.userAvatar ? ( + {response.realName} + ) : null; + } else if (response.message) { + message1 = response.message; + } else if (response.error) { + message1 = `Verification failed: ${response.error}`; + } + + return ( + + + + + {avatar &&
{avatar}
} + {realName} + {response.ranking !== undefined && ( + +

LeetCode Rank: {response.ranking}

+

Username: {username}

+
+ )} + {/* {message1} */} + Confirm registration ? +
+ + + + + + +
+
+
+
+ ); +}; + +Confirmation.propTypes = { + response: PropTypes.shape({ + realName: PropTypes.string, + ranking: PropTypes.number, + userAvatar: PropTypes.string, + message: PropTypes.string, + error: PropTypes.string + }).isRequired, + username: PropTypes.string.isRequired, + onConfirm: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +}; + +export default Confirmation; \ No newline at end of file diff --git a/src/Components/Daily.js b/src/Components/Daily.js new file mode 100644 index 0000000..d2a3dca --- /dev/null +++ b/src/Components/Daily.js @@ -0,0 +1,81 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import defaultImage from '../assets/default_file.svg'; +import SERVER_URL from "../config.js"; +import './Leaderboard.css'; + +const BASE_URL = SERVER_URL + 'api/leaderboard'; + +const Daily = () => { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + axios.get(`${BASE_URL}/daily/`) + .then(response => { + const dataArray = Object.keys(response.data).map(key => ({ + id: key, + ...response.data[key] + })); + setData(dataArray); + setLoading(false); + }) + .catch(error => { + setError(error); + setLoading(false); + }); + }, []); + + if (loading) return
Loading...
; + if (error) return
Error loading data: {error.message}
; + + return ( +
+

CCS Ranking

+

Daily Leaderboard

+ + + + + + + + + + + {data.map((item, index) => ( + + + + + + + ))} + +
RankProfileUsernameQuestions Solved
+ + {/* {index === 0 && 1st} + {index === 1 && 2nd} + {index === 2 && 3rd} */} + {index === 0 && 1} + {index === 1 && 2} + {index === 2 && 3} + {index >= 3 && {index + 1}} + + {!item.photo_url ? ( + {item.username} + ) : ( + {item.username} { e.target.src = defaultImage }} + /> + )} + {item.username}{item.ques_solv}
+
+ ); +}; + +export default Daily; diff --git a/src/Components/Leaderboard.css b/src/Components/Leaderboard.css new file mode 100644 index 0000000..bcd62d1 --- /dev/null +++ b/src/Components/Leaderboard.css @@ -0,0 +1,94 @@ +/* LEADERBOARD ****************/ +/* LEADERBOARD ****************/ +.leaderboard-container { + background-color: var(--NAV-BGCOLOR); + border-radius: 1rem; + padding: 2rem; + margin: 1rem 0; +} + +.leaderboard-title-heading, +.leaderboard-title { + color: white; + text-align: left; + margin-bottom: 1rem; + font-family: "Poppins", sans-serif; + font-weight: 400; +} + +.leaderboard-table { + width: 100%; + border-collapse: collapse; + background-color: var(--NAV-BGCOLOR); + color: var(--CONTENT-COLOR); + border: 2px solid rgba(62, 216, 219, 0.866); + border-radius: 1rem; + overflow: hidden; +} + +.leaderboard-table th, +.leaderboard-table td { + padding: 0.9375rem; + text-align: center; + border-top: 0.0625rem solid rgba(62, 216, 219, 0.866); +} + +.leaderboard-table th { + background-color: var(--NAV-BGCOLOR); + color: white; +} + +.leaderboard-table tr:last-child td { + border-bottom: none; +} + +.leaderboard-row img { + width: 3rem; + height: 3rem; + border-radius: 50%; + object-fit: cover; + margin: 0 auto; + display: block; +} + +/* Responsive Design for Leaderboard */ +@media (max-width: 768px) { + .leaderboard-container { + padding: 1rem; + } + + .leaderboard-title-heading, + .leaderboard-title { + font-size: 0.875rem; + text-align: center !important; + } + + .leaderboard-table th, + .leaderboard-table td { + padding: 0.5rem; + font-size: 0.75rem; + } + + .leaderboard-row img { + width: 2rem; + height: 2rem; + } + + .leaderboard-table { + width: 100%; + table-layout: fixed; + } + + .leaderboard-table th, + .leaderboard-table td { + white-space: normal; + word-wrap: break-word; + } +} + +@media (max-width: 425px) { + .leaderboard-table th:last-child, + .leaderboard-table td:last-child { + display: none; + } +} \ No newline at end of file diff --git a/client/src/Components/Login.js b/src/Components/Login.js similarity index 74% rename from client/src/Components/Login.js rename to src/Components/Login.js index d583b06..7f4fd64 100644 --- a/client/src/Components/Login.js +++ b/src/Components/Login.js @@ -3,10 +3,11 @@ import ccsLogo from '../assets/ccs_logo.png'; import '../Login.css'; const Login = ({ onLogin }) => { - const clientId = process.env.REACT_APP_CLIENT_ID; - - const callbackUrl = 'http://localhost:3000/authverify'; // Replace with your actual callback URL if needed + // const clientId = process.env.REACT_APP_CLIENT_ID; + const clientId = '667465f49c13fa9047715311'; + const callbackUrl = 'https://codeboard.ccstiet.com/authverify'; + // const callbackUrl = 'http://localhost:3000/authverify'; const handleLogin = () => { const authUrl = `https://auth.ccstiet.com/auth/google?clientid=${clientId}&callback=${callbackUrl}`; window.location.href = authUrl; @@ -19,7 +20,7 @@ const Login = ({ onLogin }) => { CCS Logo
-

CCS Coding Community

+

CCS Codeboard

diff --git a/src/Components/Monthly.js b/src/Components/Monthly.js new file mode 100644 index 0000000..2eace8b --- /dev/null +++ b/src/Components/Monthly.js @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import defaultImage from '../assets/default_file.svg'; +import SERVER_URL from "../config.js"; +import './Leaderboard.css'; + +const BASE_URL = SERVER_URL + 'api/leaderboard'; + +const Monthly = () => { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + axios.get(`${BASE_URL}/monthly/`) + .then(response => { + const dataArray = Object.keys(response.data).map(key => ({ + id: key, + ...response.data[key] + })); + setData(dataArray); + setLoading(false); + }) + .catch(error => { + setError(error); + setLoading(false); + }); + }, []); + + if (loading) return
Loading...
; + if (error) return
Error loading data: {error.message}
; + + return ( +
+

CCS Ranking

+

Monthly Leaderboard

+ + + + + + + + + + + {data.map((item, index) => ( + + + + + + + ))} + +
RankProfileUsernameQuestions Solved
+ {index === 0 && 1} + {index === 1 && 2} + {index === 2 && 3} + {index >= 3 && {index + 1}} + + {!item.photo_url ? ( + {item.username} + ) : ( + {item.username} { e.target.src = defaultImage }} + /> + )} + {item.username}{item.ques_solv}
+
+ ); +}; + +export default Monthly; diff --git a/src/Components/Navbar.css b/src/Components/Navbar.css new file mode 100644 index 0000000..974df72 --- /dev/null +++ b/src/Components/Navbar.css @@ -0,0 +1,302 @@ +.NavBar { + border-right: var(--NAV-BORDER); + padding: 1rem; + background-color: var(--NAV-BGCOLOR); + width: 18rem; + min-width: 14rem; + box-sizing: border-box; + overflow-y: auto; + position: fixed; + top: 0; + left: 0; + bottom: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100vh; + z-index: 1000; + transition: width 0.3s ease, height 0.3s ease, transform 0.3s ease; +} +.divider { + font-weight: 600; + pointer-events: none; + color: var(--CONTENT-H2-COLOR); +} + +.NavBar > h2 { + font-weight: 500; + color: var(--NAV-COLOR); +} + +.NavBar ul { + list-style-type: none; + padding: 0; + margin-top: 1rem; + flex-grow: 1; + display: flex; + flex-direction: column; +} + +.NavBar li { + padding-bottom: 0.3rem; + margin-bottom: 1rem; + display: flex; + align-items: center; +} + +.NavBar li a, +.NavBar li span { + text-decoration: none; + color: var(--NAV-COLOR); + padding: 0.8rem; + display: flex; + align-items: center; + width: 100%; +} + +.NavBar li a:hover { + background-color: var(--LINK-HOVER-BGCOLOR); + color: rgb(253, 254, 254); + cursor: pointer; + border-radius: 7px; +} + +.NavBar li:active, +.NavBar li a:active, +.NavBar li span:active { + color: var(--LINK-ACTIVE-COLOR); +} + +.NavBar li:focus, +.NavBar li a:focus, +.NavBar li span:focus { + background-color: var(--LINK-HOVER-BGCOLOR); + color: rgb(253, 254, 254); + outline-offset: 4px; + border-radius: 7px; +} +.nav-icon { + margin-right: 0.5rem; + font-size: 1.5rem; +} + +.NavBar .logout-button-container { + display: flex; + justify-content: flex-end; +} + +.NavBar .logout-button-container .logout-button { + background-color: rgba(62, 216, 219, 0.866); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 0.25rem; + margin-right: 1rem; + cursor: pointer; + font-size: 1rem; +} + +.logout-icon { + font-size: 1.5rem; + margin-right: 0.5rem; +} + +.logout-text { + display: inline; +} +@media (max-width: 1235px) { + .NavBar { + width: 100%; + height: 7rem; + flex-direction: row; + align-items: center; + justify-content: space-around; + border-right: none; + border-bottom: var(--NAV-BORDER); + } + + .NavBar ul { + flex-direction: row; + align-items: center; + width: 70%; + justify-content: space-around; + margin-top: 0; + } + + .NavBar li { + margin: 0; + padding: 0; + } + + .NavBar li a, + .NavBar li span { + padding: 0.2rem; + padding-bottom: 0; + } + + .nav-icon { + font-size: 1.5rem; + margin: 0; + } + + .ccsLogo { + height: 4.5rem; + margin-left: 0.5rem; + } + + .NavBar > h2, + .NavBar li a span, + .NavBar li span span { + display: none; + } + + .NavBar .divider { + display: none; + } +} + +@media (max-width: 768px) { + .NavBar { + height: auto; + padding: 0.5rem; + } + + .NavBar ul { + flex-wrap: wrap; + } + + .NavBar li { + margin: 0.25rem; + } + + .ccsLogo { + height: 3.5rem; + } +} + + @media (max-width: 1235px) { + .NavBar { + width: 100%; + height: 7rem; + position: fixed; + top: 0; + left: 0; + background-color: var(--NAV-BGCOLOR); + padding: 0; + overflow: hidden; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-around; + z-index: 1000; + border-right: none; + border-bottom: var(--NAV-BORDER); + } + + .NavBar ul { + display: flex; + flex-direction: row; + align-items: right; + width: 70%; + justify-content: space-around; + } + + .NavBar li { + margin: 0; + padding: 0; + } + + .NavBar li a, + .NavBar li span { + padding: 0.2rem; + padding-bottom: 0; + } + + .nav-icon { + font-size: 1.5rem; + margin: 0; + } + + .ccsLogo { + margin-top: 1rem; + height: 4.5rem; + margin-left: 0.5rem; + } + + .NavBar > h2, + .NavBar li a span, + .NavBar li span span { + display: none; + } + + .NavBar .divider { + display: none; + } + } + + @media (max-width: 768px) { + .NavBar .logout-button-container .logout-button { + font-size: 0.75rem; + padding: 0.15rem 0.4rem; + } + } + + +.NavBar .logout-button-container .logout-button { + display: flex; + align-items: center; + background-color: rgba(62, 216, 219, 0.866); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 0.25rem; + cursor: pointer; + font-size: 1rem; + } + + .logout-icon { + font-size: 1.5rem; + margin-right: 0.5rem; + } + + .logout-text { + display: inline; + } + + @media (max-width: 1235px) { + .logout-text { + display: none; + } + .NavBar .logout-button-container .logout-button { + background-color: transparent; + margin-top: 0.7rem; + } + .logout-button { + padding: 0.5rem; + background-color: transparent; + } + + .logout-icon { + font-size: 1.5rem; + } + } + + +@media (max-width: 1235px) { + .content { + margin-left: 0; + margin-top: 7rem; + } +} + +@media (max-width: 768px) { + .content { + margin-top: 8rem; + } +} +@media (max-width: 768px) { + .NavBar .logout-button-container .logout-button { + font-size: 0.75rem; + padding: 0.15rem 0.2rem; + } +} \ No newline at end of file diff --git a/src/Components/Navbar.js b/src/Components/Navbar.js new file mode 100644 index 0000000..d64568f --- /dev/null +++ b/src/Components/Navbar.js @@ -0,0 +1,89 @@ +import React from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { FaCalendarDay, FaCalendarWeek, FaCalendarAlt, FaSignOutAlt } from 'react-icons/fa'; +import { MdAccountCircle,MdExitToApp } from 'react-icons/md'; +import ccsLogo from '../assets/ccs_logo.png'; +import SERVER_URL from "../config.js"; +import './Navbar.css'; + +const API_URL = `${SERVER_URL}api/auth`; + +export default function Navbar() { + const navigate = useNavigate(); + + const handleLogout = async () => { + const token = localStorage.getItem('token'); + + try { + // console.log('Stored Token:', token); + + const response = await fetch(API_URL + '/logout/', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Token ${token}`, + }, + }); + + if (!response.ok) { + throw new Error('Logout request failed'); + } + + const data = await response.json(); + // console.log('Logout Response:', data); + + if (data.message.toLowerCase() === 'logged out successfully') { + localStorage.removeItem('token'); + localStorage.removeItem('isNewUser') + navigate('/login'); + } else { + throw new Error('Logout failed'); + } + } catch (error) { + console.error('Error logging out:', error); + // Optionally, you can navigate to login or show a user-friendly message + navigate('/login'); + } + }; + + return ( +
+ ccs_logo +
    +
  • DASHBOARD
  • +
  • + + + Profile + +
  • + +
  • LEADERBOARD
  • +
  • + + + Daily + +
  • +
  • + + + Weekly + +
  • +
  • + + + Monthly + +
  • +
+
+ +
+
+ ); +} diff --git a/src/Components/Profile.css b/src/Components/Profile.css new file mode 100644 index 0000000..c6bace5 --- /dev/null +++ b/src/Components/Profile.css @@ -0,0 +1,235 @@ +/* Profile ****************/ +.profile-container { + display: flex; + justify-content: space-between; /* Space between containers */ + background-color: #282c34; + color: var(--CONTENT-COLOR); + padding: 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + /* Adjusted width to fit both containers */ + margin: 0 auto; + margin-top: 50px; /* Add margin-top for centering */ + position: relative; /* Ensure it's centered vertically */ + } + + .detail-container { + flex: 1; /* Flex to adjust with the container */ + text-align: center; + padding-right: 1rem; /* Padding to add space between containers */ + } + + .detail-container img { + border-radius: 50%; + width: 150px; + height: 150px; + object-fit: cover; + margin-bottom: 20px; + border: 3px solid var(--CONTENT-H2-COLOR); /* Add border */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); /* Add shadow */ + } + + .detail-container h1 { + font-size: 2.5em; /* Increase font size */ + color: white; /* Change name color to white */ + margin-bottom: 10px; + } + + .detail-container p { + font-size: 1.2em; + color: var(--CONTENT-COLOR); + margin: 5px 0; + } + + .detail-container p span { + font-weight: bold; + color: white; /* Ensure high contrast */ + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + } + + /* Loading and Error Styles */ + .loading, + .error { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + font-size: 1.5em; + color: rgb(33, 35, 42); + } + + /* Rank Slider ****************/ + .rank-slider button { + background: none; + border: none; + padding: 0; + cursor: pointer; + font: inherit; + outline: inherit; + } + .rank-slider { + display: flex; + justify-content: center; + margin-bottom: 20px; + } + + .rank-slider .tab { + text-decoration: none; /* Remove default underline */ + color: var(--CONTENT-COLOR); + padding: 10px 20px; + margin: 0 5px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 1rem; + transition: text-decoration-color 0.3s; /* Add transition for underline color */ + } + + .rank-slider .tab.active { + text-decoration: underline; /* Underline when active */ + text-decoration-color: rgba(62, 216, 219, 0.866); /* Active underline color */ + text-underline-offset: 2px; /* Optional: adjust underline position */ + } + + /* Responsive Design for Rank Slider */ + @media (max-width: 768px) { + .rank-slider .tab { + padding: 5px 10px; + font-size: 0.875rem; + } + } + + /* Adding responsive design */ + @media (max-width: 768px) { + .profile-container { + padding: 1rem; + margin-top: 2rem; + } + + .profile-container h1 { + font-size: 1.5em; + } + + .profile-container p { + font-size: 1em; + } + } + + .question-container { + flex: 2; /* Flex to adjust with the container */ + } + + .question-container h2 { + color: white !important; /* Change heading color to white */ + text-align: center; + margin-bottom: 0.5rem; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + } + + /* LEADERBOARD ****************/ + .questions-table, + .leaderboard-table { + width: 100%; + border-collapse: collapse; + background-color: var(--NAV-BGCOLOR); + color: var(--CONTENT-COLOR); + border: 2px solid rgba(62, 216, 219, 0.866); + border-radius: 1rem; + overflow: hidden; /* Ensure the border-radius applies to the entire table */ + } + + .questions-table th, + .questions-table td, + .leaderboard-table th, + .leaderboard-table td { + padding: 0.9375rem; /* 15px in rem */ + text-align: left; + border-top: 0.0625rem solid rgba(62, 216, 219, 0.866); /* 1px in rem */ + } + + .questions-table a { + color: white; /* Change link color to white */ + text-decoration: none; /* Remove underline if desired */ + } + + .questions-table a:hover { + text-decoration: underline; /* Optional: Add underline on hover */ + color: rgba(62, 216, 219, 0.866); /* Optional: Change color on hover */ + } + + .questions-table th, + .leaderboard-table th { + background-color: var(--NAV-BGCOLOR); + color: white; + text-align: center; + } + + /* Responsive Design for Leaderboard */ + @media (max-width: 768px) { + .profile-container { + padding: 1rem; /* Equal padding on all sides */ + margin: 0; + } + + .content { + padding: 0.5rem; /* Equal padding on all sides */ + margin: 0; /* Remove any extra margin */ + } + .question-container, + .leaderboard-container { + padding: 1rem; /* Adjust padding for smaller screens */ + margin: 0.5rem; /* Adjust margin for smaller screens */ + } + .question-container h2, + .leaderboard-title-heading, + .leaderboard-title { + font-size: 0.875rem; /* Adjust font size for smaller screens */ + text-align: center !important; /* Center-align text for smaller screens */ + } + .questions-table th, + .questions-table td, + .leaderboard-table th, + .leaderboard-table td { + padding: 0.5rem; /* Reduce padding for smaller screens */ + font-size: 0.75rem; /* Reduce font size for smaller screens */ + text-align: center; + } + + .leaderboard-row img { + width: 2rem; /* Reduce image size for smaller screens */ + height: 2rem; /* Reduce image size for smaller screens */ + } + .questions-table, + .leaderboard-table { + width: 100%; /* Ensure the table width fits within the container */ + table-layout: fixed; /* Ensure consistent table cell widths */ + } + .questions-table th, + .questions-table td, + .leaderboard-table th, + .leaderboard-table td { + white-space: normal; /* Allow text to wrap within cells */ + word-wrap: break-word; /* Ensure long words are wrapped */ + } + } + + @media (max-width: 768px) { + .profile-container { + flex-direction: column; + align-items: center; + } + + .detail-container { + padding-right: 0; + margin-bottom: 1rem; /* Add space between the containers */ + } + + .question-container { + width: 100%; /* Ensure the question container takes full width */ + } + } + \ No newline at end of file diff --git a/client/src/Components/Profile.js b/src/Components/Profile.js similarity index 53% rename from client/src/Components/Profile.js rename to src/Components/Profile.js index 9067a85..870db90 100644 --- a/client/src/Components/Profile.js +++ b/src/Components/Profile.js @@ -1,22 +1,25 @@ import React, { useState, useEffect } from 'react'; import SERVER_URL from "../config.js"; -const BASE_URL = SERVER_URL+'/api/leaderboard'; +import './Profile.css'; + +const BASE_URL = SERVER_URL + 'api/leaderboard'; const Profile = () => { const [profile, setProfile] = useState(null); const [questions, setQuestions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [rankPeriod, setRankPeriod] = useState('today'); useEffect(() => { const fetchProfile = async () => { - console.log('Fetching profile...'); + // console.log('Fetching profile...'); const token = localStorage.getItem('token'); - console.log(token); + // console.log('Token:', token); if (!token) { setError('Token is missing'); setLoading(false); - window.location.href="/login"; + window.location.href = "/login"; return; } @@ -29,25 +32,29 @@ const Profile = () => { } }); - if (!response.ok) { + if (response.status === 401 || response.status === 404 || response.status === 400) { + window.location.href = "/login"; + localStorage.removeItem('token'); + return; + } + else if (!response.ok) { throw new Error(`Error: ${response.status}`); } const data = await response.json(); - console.log('Response received:', data); + // console.log('Profile response received:', data); setProfile(data); - console.log('Profile data set:', data); } catch (err) { console.error('Error occurred:', err); - setError(err); + setError(err.message); } finally { setLoading(false); - console.log('Loading state set to false'); + // console.log('Loading state set to false'); } }; const fetchQuestions = async () => { - console.log('Fetching questions...'); + // console.log('Fetching questions...'); const token = localStorage.getItem('token'); if (!token) { setError('Token is missing'); @@ -68,45 +75,55 @@ const Profile = () => { } const data = await response.json(); - console.log('Questions response received:', data); + // console.log('Questions response received:', data); setQuestions(data); - console.log('Questions data set:', data); } catch (err) { console.error('Error occurred:', err); - setError(err); + setError(err.message); } }; fetchProfile(); fetchQuestions(); - }, []); + }, []); // Fetch profile and questions only once on component mount + + const showRank = (period) => { + setRankPeriod(period); + }; if (loading) { - console.log('Loading...'); + // console.log('Loading...'); return
Loading...
; } + if (error) { - console.error('Error:', error.message); - return
Error: {error.message}
; + console.error('Error:', error); + return
Error: {error}
; } + if (!profile) { - console.log('No profile data available'); + // console.log('No profile data available'); return
No profile data available
; } - console.log('Rendering profile:', profile); - console.log('Rendering questions:', questions); + /* console.log('Rendering profile:', profile); + console.log('Rendering questions:', questions); */ return (
- Profile + {profile.photo_url && Profile}

{profile.name}

-

LeetCode Username: {profile.username}

-

LeetCode Rank: {profile.leetcode_rank}

-

Today's Rank: {profile.daily_rank}

-

Weekly Rank: {profile.weekly_rank}

-

Monthly Rank: {profile.monthly_rank}

+

Username: {profile.username}

+ + {rankPeriod === 'today' &&

Today's Rank: {profile.daily_rank}

} + {rankPeriod === 'weekly' &&

Weekly Rank: {profile.weekly_rank}

} + {rankPeriod === 'monthly' &&

Monthly Rank: {profile.monthly_rank}

} +
+ + + +
@@ -117,18 +134,20 @@ const Profile = () => { Question Difficulty Status - Link - {questions.map((question, index) => ( + {questions.length > 0 ? questions.map((question, index) => ( - {question.title} + {question.title} {question.difficulty} {question.status} - View - ))} + )) : ( + + No questions available + + )}
diff --git a/src/Components/ProfileNew.css b/src/Components/ProfileNew.css new file mode 100644 index 0000000..581f524 --- /dev/null +++ b/src/Components/ProfileNew.css @@ -0,0 +1,144 @@ +/* Profile ****************/ +.profile-container { + display: flex; + justify-content: space-between; /* Space between containers */ + background-color: #282c34; + color: var(--CONTENT-COLOR); + padding: 2rem; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + /* Adjusted width to fit both containers */ + margin: 0 auto; + margin-top: 50px; /* Add margin-top for centering */ + position: relative; /* Ensure it's centered vertically */ + } + + .detail-container { + flex: 1; /* Flex to adjust with the container */ + text-align: center; + padding-right: 1rem; /* Padding to add space between containers */ + } + + .detail-container img { + border-radius: 50%; + width: 150px; + height: 150px; + object-fit: cover; + margin-bottom: 20px; + border: 3px solid var(--CONTENT-H2-COLOR); /* Add border */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); /* Add shadow */ + } + + .detail-container h1 { + font-size: 2.5em; /* Increase font size */ + color: white; /* Change name color to white */ + margin-bottom: 10px; + } + + .detail-container p { + font-size: 1.2em; + color: var(--CONTENT-COLOR); + margin: 5px 0; + } + + .detail-container p span { + font-weight: bold; + color: white; /* Ensure high contrast */ + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + } + + + .question-container { + flex: 2; /* Flex to adjust with the container */ + } + + .question-container h2 { + color: white; /* Change heading color to white */ + text-align: center; + margin-bottom: 0.5rem; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + } + .questions-table, +.leaderboard-table { + width: 100%; + border-collapse: collapse; + background-color: var(--NAV-BGCOLOR); + color: var(--CONTENT-COLOR); + border: 2px solid rgba(62, 216, 219, 0.866); + border-radius: 1rem; + overflow: hidden; /* Ensure the border-radius applies to the entire table */ + +} + +.questions-table th, +.questions-table td, +.leaderboard-table th, +.leaderboard-table td { + padding: 0.9375rem; /* 15px in rem */ + text-align: centre; + border-top: 0.0625rem solid rgba(62, 216, 219, 0.866); /* 1px in rem */ +} +.questions-table th:first-child, +.questions-table td:first-child{ + text-align: left; +} +.questions-table a { + color: white; /* Change link color to white */ + text-decoration: none; /* Remove underline if desired */ +} + +.questions-table a:hover { + text-decoration: underline; /* Optional: Add underline on hover */ + color: rgba(62, 216, 219, 0.866); /* Optional: Change color on hover */ +} +.questions-table th, +.leaderboard-table th { + background-color: var(--NAV-BGCOLOR); + color: white; + text-align: center; +} + + /* Rank Slider ****************/ + .rank-slider button { + background: none; + border: none; + padding: 0; + cursor: pointer; + font: inherit; + outline: inherit; +} +.rank-slider { + display: flex; + justify-content: center; + margin-bottom: 20px; +} + +.rank-slider .tab { + text-decoration: none; /* Remove default underline */ + color: var(--CONTENT-COLOR); + padding: 10px 20px; + margin: 0 5px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 1rem; + transition: text-decoration-color 0.3s; /* Add transition for underline color */ +} + +.rank-slider .tab.active { + text-decoration: underline; /* Underline when active */ + text-decoration-color: rgba(62, 216, 219, 0.866); /* Active underline color */ + text-underline-offset: 2px; /* Optional: adjust underline position */ +} + +/* Responsive Design for Rank Slider */ +@media (max-width: 768px) { + .rank-slider .tab { + padding: 5px 10px; + font-size: 0.875rem; + } +} diff --git a/src/Components/UsernameEntry.js b/src/Components/UsernameEntry.js new file mode 100644 index 0000000..dd92f3e --- /dev/null +++ b/src/Components/UsernameEntry.js @@ -0,0 +1,225 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import SERVER_URL from "../config.js"; +import ccsLogoBulb from '../assets/ccs-bulb.png'; +import Confirmation from './Confirmation'; +import './UsernameForm.css'; + +const API_URL = SERVER_URL + 'api/auth'; + +const UsernameEntry = ({ setIsAuthenticated }) => { + const location = useLocation(); + const { userData, token: locationToken } = location.state || {}; + const [leetcodeUsername, setLeetcodeUsername] = useState(''); + const [showConfirmation, setShowConfirmation] = useState(false); + const [verificationResponse, setVerificationResponse] = useState({}); + const navigate = useNavigate(); + + useEffect(() => { + document.body.classList.add('username-entry-body'); + return () => { + document.body.classList.remove('username-entry-body'); + }; + }, []); + + const handleUsernameChange = (e) => { + setLeetcodeUsername(e.target.value); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + const token = locationToken || localStorage.getItem('token'); + if (!token) { + console.error('Token is missing'); + navigate('/login'); + return; + } + + const payload = { + leetcode_username: leetcodeUsername, + token: token + }; + + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Token ${token}` + }, + body: JSON.stringify(payload) + }; + + try { + const verifyResponse = await fetch(`${API_URL}/verify-leetcode/`, requestOptions); + if (!verifyResponse.ok) { + throw new Error('Verification failed'); + } + + const verifyData = await verifyResponse.json(); + setVerificationResponse(verifyData); + setShowConfirmation(true); + } catch (error) { + console.error('Error:', error.message); + alert('An error occurred. Please try again.'); + } + }; + + const handleConfirm = async () => { + const token = locationToken || localStorage.getItem('token'); + const payload = { + leetcode_username: leetcodeUsername, + token: token + }; + + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Token ${token}` + }, + body: JSON.stringify(payload) + }; + + try { + const registerResponse = await fetch(`${API_URL}/register-leetcode/`, requestOptions); + if (!registerResponse.ok) { + throw new Error('Registration failed'); + } + + const registerData = await registerResponse.json(); + // console.log('Registration successful. Redirecting to profile...'); + + await localStorage.setItem('token', token); + const storedToken = localStorage.getItem('token'); + // console.log('Stored token:', storedToken); + + setIsAuthenticated(true); + navigate('/profile'); + } catch (error) { + console.error('Error:', error); + alert('An error occurred. Please try again.'); + } + }; + + const handleCancel = () => { + setShowConfirmation(false); + }; + + return ( +
+
+
+ {/* Left side content */} +
+
+
+ CCS Logo +
+
+

Single Sign On

+

Simplifying access across CCS

+
+
+ +
+
+
+

Welcome, {userData ? userData.fullName : 'User'}!

+

Please accept the OAuth policy and provide additional information.

+
+
+
+
+ + {/* Right side form */} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + {showConfirmation && ( + + )} +
+
+ ); +}; + +export default UsernameEntry; \ No newline at end of file diff --git a/src/Components/UsernameForm.css b/src/Components/UsernameForm.css new file mode 100644 index 0000000..bbde45c --- /dev/null +++ b/src/Components/UsernameForm.css @@ -0,0 +1,494 @@ + +/* UsernameEntry styles *****************************************/ +body.username-entry-body { + background-color: black; /* Black background for the body */ + color: var(--text-color); /* White text color */ + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + padding: 0; + margin: 0; + } + .container { + background-color: var(--cont-color); + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 30px; + max-width: 1000px; + width: 100%; + text-align: center; + display: flex; + justify-content: space-evenly; + align-items: stretch; + flex-wrap: wrap; + } + + /* Left Column Styles */ + .left { + display: flex; + flex-direction: column; + width: 45%; + + border-radius: 8px 0 0 8px; + align-items: center; + gap: 30px; + background: var(--cont-color); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 12px; + } + + .left-top { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + margin-top: 30px; + gap: 15px; + text-align: center; + } + + .left-top h1 { + font-size: 32px; + color: var(--ac-color); + margin: 0; + font-weight: 700; + } + + .left-top h2 { + font-size: 16px; + color: white !important; + font-weight: 500; + } + + .left-bottom { + display: flex; + flex-direction: column; + align-items: center; + + width: 100%; + } + + .left-bottom h4 { + font-size: 20px; + color: var(--ac-color); + + font-weight: 600; + } + + .logo img { + width: 5rem; + border-radius: 50%; + } + + .left-bottom .content { + display: flex; + align-items: center; + margin: 0.5rem; + padding: 0.4rem; + } + + .profile { + width: 60px; + height: 60px; + margin-right: 10px; /* Adjust margin as needed */ + } + + .content-left { + flex: 1; /* Take up remaining space */ + + } + @media (max-width: 1024px) { + .outer-container{ + width: 90%; + } + } + /* Adjustments for smaller screens */ + @media (max-width: 768px) { + .outer-container{ + width: 82%; + margin-top: 20rem; + } + .left { + flex-direction: column; + align-items: center; + text-align: center; + } + + .content { + flex-direction: column; + align-items: center; + } + + .profile { + margin-bottom: 10px; /* Adjust margin for spacing */ + } + } + /* Right Column Styles */ + .right { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + margin-left: 8px; + width: 50%; + margin-bottom: 8px; + } + + /* Form Styles */ + .user-form { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + height: 100%; + justify-content: space-between; + } + + .input-group-row { + display: flex; + gap: 15px; + } + + .input-group { + position: relative; + margin-top: 10px; + height: 56px; + } + + .input-group input, + .input-group select { + width: 100%; + height: 100%; + padding: 0 10px; + border: 1px solid var(--ac-color); + border-radius: 4px; + font-size: 14px; + line-height: 56px; + background-color: var(--cont-color); + color: var(--text-color); /* White text color */ + } + .input-group #roll-number, + .input-group #email{ + color: rgb(179, 179, 179); + } + + .input-group label { + position: absolute; + top: 0; + left: 0; + font-size: 12px; + color: var(--ac-color); + transform: translateY(-50%) scale(0.75) translateX(-5%); + background: var(--cont-color); + pointer-events: none; + transition: none; + } + + .input-group input:focus ~ label, + .input-group input:not(:placeholder-shown) ~ label, + .input-group select:focus ~ label, + .input-group select:not([value=""]) ~ label { + top: 0; + } + + .input-group select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%235f6368' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 40px; + } + .input-group input:focus { + background-color: var(--cont-color); /* Dark background */ + color: var(--text-color); /* White text */ + border-color: var(--ac-color); /* Accent color border */ + } + + /* Optional: Style for when the input is not empty */ + .input-group input:not(:placeholder-shown) { + background-color: var(--cont-color); /* Dark background */ + color: var(--text-color); /* White text */ + } + .input-group select option[disabled] { + display: none; + } + + .input-group select:invalid { + color: #5f6368; + } + + /* Policy Styles */ + .policy { + display: flex; + align-items: center; + } + + .policy label { + font-size: 14px; + color: #5f6368; + display: flex; + align-items: center; + } + + .policy input[type="checkbox"] { + width: auto; + margin-right: 10px; + } + + /* Button Styles */ + button { + background-color: var(--ac-color); + color: #ffffff; + padding: 10px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + } + + button:hover { + background-color: #139ad6; + } + + /* Media Queries */ + @media (max-width: 768px) { + + .container { + margin-top: 6rem; + flex-direction: column; + } + + .left, + .right { + width: 100%; + margin: 0; + } + } + + @media (max-width: 576px) { + .container { + box-shadow: none; + } + + .left { + + box-shadow: none; + } + + .left-top h1 { + font-size: 24px; + } + + .left-top h2 { + font-size: 14px; + } + + .left-bottom h4 { + font-size: 18px; + } + + .content p { + font-size: 14px; + } + + .input-group label { + top: 0; + } + + .input-group { + height: 45px; + } + + .input-group input, + .input-group select { + width: 100%; + height: 40px; + padding: 0 10px; + border: 1px solid #39b9bc; + border-radius: 4px; + font-size: 14px; + line-height: 1.5; + background-color: var(--cont-color); + } + + .policy label { + font-size: 12px; + } + + button { + padding: 12px; + font-size: 16px; + } + + .input-group label { + font-size: 12px; + } + + .input-group input:focus ~ label, + .input-group input:not(:placeholder-shown) ~ label, + .input-group select:focus ~ label, + .input-group select:not([value=""]):not([value="Select your branch"]) ~ label { + font-size: 10px; + } + } + + @media (max-width: 480px) { + .input-group-row { + flex-direction: column; + } + + .input-group-row .input-group { + width: 100%; + } + } + @media (max-width: 600px) { + body{ + background-color: rgb(18, 17, 17); + } + } + @media (max-width: 445px) { + + .container { + margin-top: 0; + flex-direction: column; + } + } + + + /* CONFIRMATION POPUP */ + .AlertDialogOverlay { + background-color: rgba(0, 0, 0, 0.455); + position: fixed; + inset: 0; + animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); + } + + .AlertDialogContent { + background-color: rgb(51, 55, 62); + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + color: white; + border-radius: 6px; + box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 90vw; + max-width: 500px; + max-height: 85vh; + padding: 25px; + animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); + text-align: center; + } + + .AlertDialogContent:focus { + outline: none; + } + + .AlertDialogTitle { + margin: 0; + color: white; + font-size: 17px; + font-weight: 500; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + } + + .AlertDialogDescription { + margin-top: 10px; + margin-bottom: 20px; + color: white; + font-size: 15px; + line-height: 1.5; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + } + + .Button { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + padding: 0 15px; + font-size: 15px; + line-height: 1; + font-weight: 500; + height: 35px; + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; + } + + .Button.violet { + background-color: rgba(62, 216, 219, 0.866); + color: white; + } + + .Button.violet:hover { + background-color: rgba(55, 191, 194, 0.866); + } + + .Button.violet:focus { + box-shadow: 0 0 0 2px black; + } + + .Button.mauve { + background-color: red; + color: white; + } + + .Button.mauve:hover { + background-color: rgb(206, 5, 5); + } + + .Button.mauve:focus { + box-shadow: 0 0 0 2px black; + } + + .avatar-container { + display: flex; + justify-content: center; + margin-bottom: 15px; + } + + .avatar { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; + } + + .button-container { + display: flex; + justify-content: center; + gap: 25px; + margin-top: 20px; + } + + @keyframes overlayShow { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + @keyframes contentShow { + from { + opacity: 0; + transform: translate(-50%, -48%) scale(0.96); + } + to { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + } + \ No newline at end of file diff --git a/src/Components/Weekly.js b/src/Components/Weekly.js new file mode 100644 index 0000000..3d70b7d --- /dev/null +++ b/src/Components/Weekly.js @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import defaultImage from '../assets/default_file.svg'; +import SERVER_URL from "../config.js"; +import './Leaderboard.css'; + +const BASE_URL = SERVER_URL + 'api/leaderboard'; + +const Weekly = () => { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + axios.get(`${BASE_URL}/weekly/`) + .then(response => { + const dataArray = Object.keys(response.data).map(key => ({ + id: key, + ...response.data[key] + })); + setData(dataArray); + setLoading(false); + }) + .catch(error => { + setError(error); + setLoading(false); + }); + }, []); + + if (loading) return
Loading...
; + if (error) return
Error loading data: {error.message}
; + + return ( +
+

CCS Ranking

+

Weekly Leaderboard

+ + + + + + + + + + + {data.map((item, index) => ( + + + + + + + ))} + +
RankProfileUsernameQuestions Solved
+ {index === 0 && 1} + {index === 1 && 2} + {index === 2 && 3} + {index >= 3 && {index + 1}} + + {!item.photo_url ? ( + {item.username} + ) : ( + {item.username} { e.target.src = defaultImage }} + /> + )} + {item.username}{item.ques_solv}
+
+ ); +}; + +export default Weekly; diff --git a/src/Login.css b/src/Login.css new file mode 100644 index 0000000..39ef3dc --- /dev/null +++ b/src/Login.css @@ -0,0 +1,165 @@ +body.login-body { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: black; + color: white; + font-size: 1rem; /* Base font size */ +} + +.login-outer-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + margin: 0 auto; +} + +.login-container { + display: flex; + flex-direction: row; + background-color: rgb(18, 17, 17); + border-radius: 0.5rem; /* 8px => 0.5rem */ + overflow: hidden; + box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1); /* 4px, 8px => 0.25rem, 0.5rem */ + width: 100%; + max-width: 56.25rem; /* 900px => 56.25rem */ + height: 60%; + z-index: 1; +} + +.logo-section { + flex: 1; + padding-left: 2rem; + display: flex; + justify-content: center; + align-items: center; +} + +.ccs-logo { + max-width: 90%; + height: auto; +} +.logo-section p { + color: white; +} +.form-section { + flex: 1; + padding: 2.5rem; /* 40px => 2.5rem */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.form-section h2 { + text-align: center; + margin-bottom: 1.25rem; /* 20px => 1.25rem */ + color: white; +} + +.form-button { + width: 70%; + padding: 0.625rem; /* 10px => 0.625rem */ + margin-top: 0.625rem; /* 10px => 0.625rem */ + background-color: #4285f4; + color: white; + border: none; + border-radius: 0.5rem; /* 8px => 0.5rem */ + cursor: pointer; +} + +.form-button:hover { + background-color: #357ae8; +} + +.ccs-login { + background-color: #34a853; +} + +.ccs-login:hover { + background-color: #2b8e42; +} + +/* Media Queries for responsiveness */ +@media (max-width: 768px) { + .login-container { + flex-direction: column; + height: auto; + margin-top: 1.25rem; /* 20px => 1.25rem */ + margin: 1.25rem; /* 20px => 1.25rem */ + width: 90%; /* Reduce container width */ + } + + .logo-section { + padding: 1.25rem; /* 20px => 1.25rem */ + } + + .ccs-logo { + max-width: 60%; + } + + .form-section { + padding: 1.25rem; /* 20px => 1.25rem */ + } + + .form-button { + width: 90%; + padding: 0.5rem; /* 8px => 0.5rem */ + margin-top: 0.5rem; /* 8px => 0.5rem */ + font-size: 0.875rem; /* Smaller font size */ + } + + .form-section h2 { + font-size: 1.125rem; /* 18px => 1.125rem */ + } +} + +@media (max-width: 480px) { + .login-outer-container { + min-height: 100vh; + display: flex; + align-items: flex-start; /* Align to the top */ + justify-content: center; + padding-top: 1vh; /* 1% of viewport height */ + } + + .login-container { + flex-direction: column; + height: auto; + width: 95%; + max-width: 100%; /* Ensure it doesn't exceed screen width */ + margin: 0 auto; /* Center horizontally */ + padding: 0.5rem; + } + + .ccs-logo { + max-width: 50%; + } + + .form-section { + padding: 0.625rem; /* 10px => 0.625rem */ + } + + .form-button { + width: 100%; + padding: 0.375rem; /* 6px => 0.375rem */ + margin-top: 0.375rem; /* 6px => 0.375rem */ + font-size: 0.75rem; /* 12px => 0.75rem */ + } + + .form-section h2 { + font-size: 1rem; /* 16px => 1rem */ + } +} +@media (max-width: 320px) { + .login-outer-container { + padding-top: 0.5vh; /* Even smaller top padding for very small screens */ + } + + .login-container { + padding: 0.25rem; + } +} \ No newline at end of file diff --git a/src/assets/badge1.png b/src/assets/badge1.png new file mode 100644 index 0000000..75b568a Binary files /dev/null and b/src/assets/badge1.png differ diff --git a/src/assets/badge2.png b/src/assets/badge2.png new file mode 100644 index 0000000..615fad2 Binary files /dev/null and b/src/assets/badge2.png differ diff --git a/src/assets/badge3.png b/src/assets/badge3.png new file mode 100644 index 0000000..fd6a4a1 Binary files /dev/null and b/src/assets/badge3.png differ diff --git a/src/assets/ccs-bulb.png b/src/assets/ccs-bulb.png new file mode 100644 index 0000000..4f907f3 Binary files /dev/null and b/src/assets/ccs-bulb.png differ diff --git a/client/src/assets/ccs_logo.png b/src/assets/ccs_logo.png similarity index 100% rename from client/src/assets/ccs_logo.png rename to src/assets/ccs_logo.png diff --git a/src/assets/default_file.svg b/src/assets/default_file.svg new file mode 100644 index 0000000..f297289 --- /dev/null +++ b/src/assets/default_file.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/src/assets/first1.png b/src/assets/first1.png new file mode 100644 index 0000000..eb4d590 Binary files /dev/null and b/src/assets/first1.png differ diff --git a/src/assets/second2.png b/src/assets/second2.png new file mode 100644 index 0000000..8d1f10b Binary files /dev/null and b/src/assets/second2.png differ diff --git a/src/assets/third3.png b/src/assets/third3.png new file mode 100644 index 0000000..e7c2c20 Binary files /dev/null and b/src/assets/third3.png differ diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..888a433 --- /dev/null +++ b/src/config.js @@ -0,0 +1,3 @@ +const SERVER_URL = "https://api.codeboard.ccstiet.com/"; +// const SERVER_URL = "https://api.knowishan.fun/"; +export default SERVER_URL; \ No newline at end of file diff --git a/client/src/index.js b/src/index.js similarity index 100% rename from client/src/index.js rename to src/index.js diff --git a/client/src/rules.md b/src/rules.md similarity index 100% rename from client/src/rules.md rename to src/rules.md diff --git a/src/usernameform.css b/src/usernameform.css new file mode 100644 index 0000000..f26126d --- /dev/null +++ b/src/usernameform.css @@ -0,0 +1,402 @@ +/* Root Variables */ +:root { + --cont-color: rgb(18, 17, 17); /* Dark background for containers */ + --ac-color: rgba(62, 216, 219, 0.866); /* Accent color for labels and headings */ + --text-color: #ffffff; /* White text color */ + --input-focus-color: rgb(77, 77, 77); +} + +/* Global Reset */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: Arial, sans-serif; +} +html, body { + margin: 0; + padding: 0; +} + +/* Body Styles */ +body { + background-color: #000000; /* Black background for the body */ + color: var(--text-color); /* White text color */ + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + padding: 0; + margin: 0; +} + +/* Container Styles */ +.username-entry-outer-container { + display: flex; + justify-content: center; + align-items: flex-start; + min-height: 100vh; + padding-top: 0.125vh; + padding: 0; + margin: 0; +} + +.container { + background-color: var(--cont-color); + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 20px; + max-width: 1000px; + width: 100%; + text-align: center; + display: flex; + justify-content: space-evenly; + align-items: stretch; + flex-wrap: wrap; + + margin: 0; +} +/* Left Column Styles */ +.left { + display: flex; + flex-direction: column; + width: 45%; + + border-radius: 8px 0 0 8px; + align-items: center; + gap: 30px; + background: var(--cont-color); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 12px; +} + +.left-top { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + margin-top: 30px; + gap: 15px; + text-align: center; +} + +.left-top h1 { + font-size: 32px; + color: var(--ac-color); + margin: 0; + font-weight: 700; +} + +.left-top h2 { + font-size: 16px; + color: white; + font-weight: 500; +} + +.left-bottom { + display: flex; + flex-direction: column; + align-items: center; + + width: 100%; +} + +.left-bottom h4 { + font-size: 20px; + color: var(--ac-color); + + font-weight: 600; +} + +.logo img { + width: 5rem; + border-radius: 50%; +} + +.left-bottom .content { + display: flex; + align-items: center; + margin: 0.5rem; + padding: 0.4rem; +} + +.profile { + width: 60px; + height: 60px; + margin-right: 10px; /* Adjust margin as needed */ +} + +.content-left { + flex: 1; /* Take up remaining space */ + +} + +/* Adjustments for smaller screens */ +@media (max-width: 768px) { + .left { + flex-direction: column; + align-items: center; + text-align: center; + } + + .content { + flex-direction: column; + align-items: center; + } + + .profile { + margin-bottom: 10px; /* Adjust margin for spacing */ + } +} +/* Right Column Styles */ +.right { + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + margin-left: 8px; + width: 50%; + margin-bottom: 8px; +} + +/* Form Styles */ +.user-form { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + height: 100%; + justify-content: space-between; +} + +.input-group-row { + display: flex; + gap: 15px; +} + +.input-group { + position: relative; + margin-top: 10px; + height: 56px; +} + +.input-group input, +.input-group select { + width: 100%; + height: 100%; + padding: 0 10px; + border: 1px solid var(--ac-color); + border-radius: 4px; + font-size: 14px; + line-height: 56px; + background-color: var(--cont-color); + color: var(--text-color); /* White text color */ +} +.input-group #roll-number, +.input-group #email{ + color: rgb(179, 179, 179); +} + +.input-group label { + position: absolute; + top: 0; + left: 0; + font-size: 12px; + color: var(--ac-color); + transform: translateY(-50%) scale(0.75) translateX(-5%); + background: var(--cont-color); + pointer-events: none; + transition: none; +} + +.input-group input:focus ~ label, +.input-group input:not(:placeholder-shown) ~ label, +.input-group select:focus ~ label, +.input-group select:not([value=""]) ~ label { + top: 0; +} + +.input-group select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%235f6368' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + padding-right: 40px; +} +.input-group input:focus { + background-color: var(--cont-color); /* Dark background */ + color: var(--text-color); /* White text */ + border-color: var(--ac-color); /* Accent color border */ +} + +/* Optional: Style for when the input is not empty */ +.input-group input:not(:placeholder-shown) { + background-color: var(--cont-color); /* Dark background */ + color: var(--text-color); /* White text */ +} +.input-group select option[disabled] { + display: none; +} + +.input-group select:invalid { + color: #5f6368; +} + +/* Policy Styles */ +.policy { + display: flex; + align-items: center; +} + +.policy label { + font-size: 14px; + color: #5f6368; + display: flex; + align-items: center; +} + +.policy input[type="checkbox"] { + width: auto; + margin-right: 10px; +} + +/* Button Styles */ +button { + background-color: var(--ac-color); + color: #ffffff; + padding: 10px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; +} + +button:hover { + background-color: #139ad6; +} + +/* Media Queries */ +@media (max-width: 768px) { + + .container { + margin-top: 6rem; + flex-direction: column; + } + + .left, + .right { + width: 100%; + margin: 0; + } +} + +@media (max-width: 576px) { + .container { + box-shadow: none; + } + + .left { + + box-shadow: none; + } + + .left-top h1 { + font-size: 24px; + } + + .left-top h2 { + font-size: 14px; + } + + .left-bottom h4 { + font-size: 18px; + } + + .content p { + font-size: 14px; + } + + .input-group label { + top: 0; + } + + .input-group { + height: 45px; + } + + .input-group input, + .input-group select { + width: 100%; + height: 40px; + padding: 0 10px; + border: 1px solid #39b9bc; + border-radius: 4px; + font-size: 14px; + line-height: 1.5; + background-color: var(--cont-color); + } + + .policy label { + font-size: 12px; + } + + button { + padding: 12px; + font-size: 16px; + } + + .input-group label { + font-size: 12px; + } + + .input-group input:focus ~ label, + .input-group input:not(:placeholder-shown) ~ label, + .input-group select:focus ~ label, + .input-group select:not([value=""]):not([value="Select your branch"]) ~ label { + font-size: 10px; + } +} + +@media (max-width: 480px) { + .input-group-row { + flex-direction: column; + } + + .input-group-row .input-group { + width: 100%; + } +} +@media (max-width: 600px) { + body{ + background-color: rgb(18, 17, 17); + } +} +@media (max-width: 445px) { + .username-entry-outer-container { + align-items: flex-start; + padding-top: 0.125vh; + } + .container { + + margin-bottom: 0.25rem; + margin-left: auto; + margin-right: auto; + flex-direction: column; + width: 98%; /* Increase width slightly */ + max-width: 100%; /* Ensure it doesn't exceed screen width */ + padding: 10px; + } +} +@media (max-width: 320px), (max-height: 600px) { + .username-entry-outer-container { + padding-top: 0.25rem; /* Reduced from 0.25vh to 0.125vh */ + } + .container { + margin-top: 0rem; + margin-bottom: 0.25rem; + width: 98%; + padding-top: 0.25rem; + } +} \ No newline at end of file