diff --git a/package-lock.json b/package-lock.json index cd606935..e20e01d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "leet-code-frontend", "version": "0.0.0", "dependencies": { + "bootstrap": "^5.3.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -15,6 +16,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^3.1.0", + "react-router-dom": "^6.16.0", "vite": "^4.2.0" } }, @@ -766,6 +768,25 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", + "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -829,6 +850,24 @@ "node": ">=4" } }, + "node_modules/bootstrap": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -1226,6 +1265,38 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", + "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==", + "dev": true, + "dependencies": { + "@remix-run/router": "1.9.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz", + "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==", + "dev": true, + "dependencies": { + "@remix-run/router": "1.9.0", + "react-router": "6.16.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -1853,6 +1924,18 @@ } } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true + }, + "@remix-run/router": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", + "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==", + "dev": true + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1907,6 +1990,12 @@ "color-convert": "^1.9.0" } }, + "bootstrap": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "requires": {} + }, "browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -2176,6 +2265,25 @@ "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", "dev": true }, + "react-router": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", + "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==", + "dev": true, + "requires": { + "@remix-run/router": "1.9.0" + } + }, + "react-router-dom": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz", + "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==", + "dev": true, + "requires": { + "@remix-run/router": "1.9.0", + "react-router": "6.16.0" + } + }, "resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", diff --git a/package.json b/package.json index 154dd4dd..08094e4a 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "bootstrap": "^5.3.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -16,6 +17,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^3.1.0", + "react-router-dom": "^6.16.0", "vite": "^4.2.0" } } diff --git a/src/App.jsx b/src/App.jsx index 7743965b..648d5388 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,59 +1,77 @@ -/* - * Temporary problems array schema - */ -const problems = [{ - title: "201. Bitwise AND of Numbers Range", - difficulty: "Medium", - acceptance: "42%" -},{ - title: "201. Bitwise AND of Numbers Range", - difficulty: "Medium", - acceptance: "412%" -}, - { - title: "202. Happy Number", - difficulty: "Easy", - acceptance: "54.9%" - }, - { - title: "203. Remove Linked List Elements", - difficulty: "Hard", - acceptance: "42%" - }]; - +import React, { useState } from 'react'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import Home from './pages/Home'; +import Blogs from './pages/Blogs'; +import Contact from './pages/Contact'; +import Layout from './pages/Layout'; +import Login from './pages/Login'; +import Signup from './pages/Signup'; +import SignupSuccess from './pages/SignupSucess'; +import Problems from './pages/Problems'; +import ProblemDetails from './pages/ProblemDetails'; +import NoPage from './pages/noPage'; function App() { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [problemId, setProblemId] = useState(null); + const [userId, setUserId] = useState(null); - /* Add routing here, routes look like - - /login - Login page - /signup - Signup page - /problemset/all/ - All problems (see problems array above) - /problems/:problem_slug - A single problem page - */ - - return ( -
- Finish the assignment! Look at the comments in App.jsx as a starting point -
- ) -} + // Function to simulate a login + const login = (token) => { + localStorage.setItem('Token', token); + + setIsAuthenticated(true); + }; + + // Function to simulate a logout + const logout = () => { + localStorage.removeItem('Token'); -// A demo component -function ProblemStatement(props) { - const title = props.title; - const acceptance = props.acceptance; - const difficulty = props.difficulty; - - return - - {title} - - - {acceptance} - - - {difficulty} - - + setIsAuthenticated(false); + }; + + // A function to handle authentication-based redirection + const requireAuth = (component) => { + return isAuthenticated ? component : ; + }; + + return ( + + + + } + > + } /> + } /> + } /> + } /> + } /> + + } + /> + } + /> + } /> + + )} // Protect this route with requireAuth + /> + + + ); } -export default App + +export default App; diff --git a/src/assets/logo/logo.jpg b/src/assets/logo/logo.jpg new file mode 100644 index 00000000..1bb040f5 Binary files /dev/null and b/src/assets/logo/logo.jpg differ diff --git a/src/index.css b/src/index.css index e69de29b..15ddd8a9 100644 --- a/src/index.css +++ b/src/index.css @@ -0,0 +1,67 @@ +.login-container { + max-width: 400px; + margin: 0 auto; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.login-container h2 { + text-align: center; + margin-bottom: 20px; +} + +.signup-container { + max-width: 400px; + margin: 0 auto; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.signup-container h2 { + text-align: center; + margin-bottom: 20px; +} + +.form-group { + margin-bottom: 15px; +} + +label { + display: block; + font-weight: bold; + margin-bottom: 5px; +} + +input[type='text'], +input[type='email'], +input[type='password'] { + width: 100%; + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + outline: none; +} + +/* button { + width: 100%; + padding: 10px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 5px; + cursor: pointer; + } + + button:hover { + background-color: #0056b3; + } + */ + +.logo { + width: 3.125rem; /* Set the desired width */ + height: auto; /* Automatically adjust height while maintaining aspect ratio */ +} diff --git a/src/main.jsx b/src/main.jsx index 5cc59919..f7ad52ad 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,10 +1,11 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; ReactDOM.createRoot(document.getElementById('root')).render( - , -) + +); diff --git a/src/pages/Blogs.jsx b/src/pages/Blogs.jsx new file mode 100644 index 00000000..d57e2ef2 --- /dev/null +++ b/src/pages/Blogs.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const Blogs = () => { + return ( +
+

Blogs

+
+ ); +}; + +export default Blogs; diff --git a/src/pages/Contact.jsx b/src/pages/Contact.jsx new file mode 100644 index 00000000..a4a6d514 --- /dev/null +++ b/src/pages/Contact.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const Contact = () => { + return ( +
+

Contact Us

+
+ ); +}; + +export default Contact; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx new file mode 100644 index 00000000..ff082f7f --- /dev/null +++ b/src/pages/Home.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const Home = () => { + return ( +
+

This is home page

+
+ ); +}; + +export default Home; diff --git a/src/pages/Layout.jsx b/src/pages/Layout.jsx new file mode 100644 index 00000000..cc45682f --- /dev/null +++ b/src/pages/Layout.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { Outlet, Link } from 'react-router-dom'; +import logo from './../assets/logo/logo.jpg'; + +const Layout = (props) => { + const { isAuthenticated, onLogout } = props; + return ( + <> + + + + ); +}; + +export default Layout; diff --git a/src/pages/ProblemDetails.jsx b/src/pages/ProblemDetails.jsx new file mode 100644 index 00000000..043fde13 --- /dev/null +++ b/src/pages/ProblemDetails.jsx @@ -0,0 +1,181 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { Link } from 'react-router-dom'; +import logo from './../assets/logo/logo.jpg'; + +const ProblemDetails = (props) => { + const { pid } = useParams(); + const cleanId = pid.substring(1); + const [problem, setProblem] = useState(''); + const [submission, setSubmission] = useState(''); + const [result, setResult] = useState(''); + const [showResult, setShowResult] = useState(false); // New state for result visibility + const [submissions, setSubmissions] = useState([]); + const [showSubmissions, setShowSubmissions] = useState(false); // state for Getsubmission visibility + const { userId } = props; + + const init = async () => { + const response = await fetch('http://localhost:3000/question/' + cleanId, { + method: 'GET', + }); + const json = await response.json(); + setProblem(json.question); + }; + + const handleSubmit = () => { + postSubmit(); + getSubmit(); + }; + + useEffect(() => { + init(); + }, []); + + const postSubmit = async () => { + const response = await fetch('http://localhost:3000/submissions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + authorization: localStorage.getItem('Token'), + }, + body: JSON.stringify({ + problemId: cleanId, + submission: submission, + userId: userId, + }), + }); + + const json = await response.json(); + console.log(json); + }; + + const getSubmit = async () => { + const response = await fetch( + 'http://localhost:3000/submission/' + cleanId, + { + method: 'GET', + headers: { + authorization: localStorage.getItem('Token'), + }, + } + ); + const json = await response.json(); + console.log(json); + }; + + const handleRun = async () => { + const response = await fetch('http://localhost:3000/run', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + authorization: localStorage.getItem('Token'), + }, + body: JSON.stringify({ + problemId: cleanId, + submission: submission, + }), + }); + + const json = await response.json(); + setResult(json.status); + setShowResult(true); + console.log(result); + console.log(json); + }; + + const difficultyColor = (value) => { + if (value === 'Easy') { + return 'text-success'; + } else if (value === 'Medium') { + return 'text-warning'; + } else return 'text-danger'; + }; + + const generateResult = (result) => { + if (result === 'WA') { + return Wrong Answer; + } else { + return Accepted; + } + }; + + if (!problem) { + return

Problem Not found!

; + } + return ( + <> +
+
+
+ + Your Logo + +
+
+ +

All Problems

+ +
+
+
+

+ {problem.id}. {problem.title} +

+
+
+
+

+ {problem.difficulty} +

+
+
+

Acceptance Rate : {problem.acceptanceRate}

+
+
+

Description :

+
{problem.description}
+
+
Input : {problem.input}
+
Output : {problem.output}
+
+ {showResult &&
Result : {generateResult(result)}
} +
+
+
+
+ +
+
+ + +
+
+
+ + ); +}; + +export default ProblemDetails; diff --git a/src/pages/Problems.jsx b/src/pages/Problems.jsx new file mode 100644 index 00000000..319edd34 --- /dev/null +++ b/src/pages/Problems.jsx @@ -0,0 +1,72 @@ +import { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; + +const Problems = (props) => { + const [problems, setProblems] = useState([]); + const { setProblemId } = props; + + const init = async () => { + const response = await fetch('http://localhost:3000/questions', { + method: 'GET', + }); + + const json = await response.json(); + setProblems(json.problems); + }; + + const handleProblemClick = (id) => { + console.log(`Clicked problem with ID: ${id}`); + if (id) { + setProblemId(id); + } + console.log(id); + }; + + const difficultyColor = (value) => { + if (value === 'Easy') { + return 'text-success'; + } else if (value === 'Medium') { + return 'text-warning'; + } else return 'text-danger'; + }; + + useEffect(() => { + init(); + }, []); + return ( +
+ + + + + + + + + + + {problems.map((problem, index) => ( + + + + + + + ))} + +
IDTITLEACCEPTANCE RATEDIFFICULTY
{problem.id} + handleProblemClick(problem.id)} + className="nav-link" + > + {problem.title} + + {problem.acceptanceRate} + {problem.difficulty} +
+
+ ); +}; + +export default Problems; diff --git a/src/pages/SignupSucess.jsx b/src/pages/SignupSucess.jsx new file mode 100644 index 00000000..2a97c4da --- /dev/null +++ b/src/pages/SignupSucess.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +const SignupSuccess = () => { + return ( +
+

Signup Successful

+

+ Thank you for signing up. You can now log in using your credentials. +

+ {/* You can add more content or links here */} +
+ ); +}; + +export default SignupSuccess; diff --git a/src/pages/login.jsx b/src/pages/login.jsx new file mode 100644 index 00000000..b3934274 --- /dev/null +++ b/src/pages/login.jsx @@ -0,0 +1,87 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +const Login = (props) => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [isWrong, setIsWrong] = useState(false); + const navigate = useNavigate(); + const { onLogin, problemId, setUserId } = props; + + const handleLogin = async () => { + // Implement authentication logic here + console.log('Email:', email); + console.log('Password:', password); + + const response = await fetch('http://localhost:3000/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, password }), + }); + + if (response.ok) { + // Successful login + const json = await response.json(); + const token = json.token; + const userId = json.userId; + onLogin(token); + const url = `/problem/:${problemId}`; + if (problemId) { + setUserId(userId); + navigate(url); + } else { + navigate('/problems'); // Redirect to /problems if problemId is not available + } + } else { + // Error occurred + const json = await response.json(); // Get the error message as text + const errorResponse = json.message; + setErrorMessage(errorResponse); // Set the error message state + setIsWrong(true); + } + }; + + return ( + <> + {isWrong ? ( +

{errorMessage}

+ ) : ( +
+

Login

+
+
+ + setEmail(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+ +
+
+ )} + + ); +}; + +export default Login; diff --git a/src/pages/noPage.jsx b/src/pages/noPage.jsx new file mode 100644 index 00000000..bdf70eff --- /dev/null +++ b/src/pages/noPage.jsx @@ -0,0 +1,5 @@ +const NoPage = () => { + return

404

; +}; + +export default NoPage; diff --git a/src/pages/signup.jsx b/src/pages/signup.jsx new file mode 100644 index 00000000..b44277e9 --- /dev/null +++ b/src/pages/signup.jsx @@ -0,0 +1,91 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +const Signup = () => { + const [userName, setUserName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [isWrong, setIsWrong] = useState(false); + const navigate = useNavigate(); + + const handleSignup = async () => { + // Implement user registration logic here + console.log('User Name:', userName); + console.log('Email:', email); + console.log('Password:', password); + + const response = await fetch('http://localhost:3000/signup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + userName, + email, + password, + }), + }); + + if (response.ok) { + const json = await response.json(); + console.log(json); + navigate('/signupsuccess'); + } else { + const json = await response.json(); + const errorResponse = json.message; + setErrorMessage(errorResponse); + setIsWrong(true); + } + }; + + return ( + <> + {isWrong ? ( +

{errorMessage}

+ ) : ( +
+

Sign Up

+
+
+ + setUserName(e.target.value)} + /> +
+
+ + setEmail(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+ +
+
+ )} + + ); +}; + +export default Signup; diff --git a/vite.config.js b/vite.config.js index 5a33944a..627a3196 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) +});