diff --git a/paras/package-lock.json b/paras/package-lock.json index 09405fc..0c24e8c 100644 --- a/paras/package-lock.json +++ b/paras/package-lock.json @@ -13,6 +13,7 @@ "@testing-library/user-event": "^14.5.2", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0", "react-scripts": "^5.0.1", "web-vitals": "^4.2.3" } @@ -3183,6 +3184,15 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", + "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -13919,6 +13929,38 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", + "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.20.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", + "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.20.0", + "react-router": "6.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/paras/package.json b/paras/package.json index bfcdd66..26c3a95 100644 --- a/paras/package.json +++ b/paras/package.json @@ -8,6 +8,7 @@ "@testing-library/user-event": "^14.5.2", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0", "react-scripts": "^5.0.1", "web-vitals": "^4.2.3" }, diff --git a/paras/src/App.css b/paras/src/App.css index 290af52..f01d7d8 100644 --- a/paras/src/App.css +++ b/paras/src/App.css @@ -21,7 +21,7 @@ body { justify-content: space-between; align-items: center; - padding: 10px 20px; /* Adjusted padding for better spacing */ + padding: 40px 20px; /* Adjusted padding for better spacing */ background: rgb(204, 171, 234); /* Gradient background */ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); border-radius: 8px; /* Rounded corners for a softer look */ diff --git a/paras/src/App.js b/paras/src/App.js index 800d2a4..55cf855 100644 --- a/paras/src/App.js +++ b/paras/src/App.js @@ -1,190 +1,19 @@ -import React, { useState, useEffect } from 'react'; -import './App.css'; -import Sparkle from './Sparkle'; // If Sparkle is not used, you can remove this import. - -const initialBoard = Array(9).fill(null); - -const App = () => { - const [board, setBoard] = useState(initialBoard); - const [currentPlayer, setCurrentPlayer] = useState('X'); - const [winner, setWinner] = useState(null); - const [draw, setDraw] = useState(false); - const [theme, setTheme] = useState('system'); - const [isDarkMode, setIsDarkMode] = useState(false); - const [scorePlayerX, setScorePlayerX] = useState(0); - const [scorePlayerO, setScorePlayerO] = useState(0); - const [highestScorePlayerX, setHighestScorePlayerX] = useState(0); - const [highestScorePlayerO, setHighestScorePlayerO] = useState(0); - const [gameMode, setGameMode] = useState(null); - - // Handle click on a cell - const handleCellClick = (index) => { - if (board[index] || winner || draw) return; - - const newBoard = [...board]; - newBoard[index] = currentPlayer; - setBoard(newBoard); - checkWinner(newBoard, currentPlayer); - setCurrentPlayer(currentPlayer === 'X' ? 'O' : 'X'); - }; - - // Check if there's a winner - const checkWinner = (board, player) => { - const winningCombinations = [ - [0, 1, 2], [3, 4, 5], [6, 7, 8], - [0, 3, 6], [1, 4, 7], [2, 5, 8], - [0, 4, 8], [2, 4, 6] - ]; - for (let combination of winningCombinations) { - const [a, b, c] = combination; - if (board[a] === player && board[b] === player && board[c] === player) { - setWinner(player); - updateScoreAndHighestScore(player); - return; - } - } - if (board.every(cell => cell !== null) && !winner) { - setDraw(true); - } - }; - - // Update score and highest score - const updateScoreAndHighestScore = (player) => { - if (player === 'X') { - const newScoreX = scorePlayerX + 1; - setScorePlayerX(newScoreX); - if (newScoreX > highestScorePlayerX) { - setHighestScorePlayerX(newScoreX); - } - } else if (player === 'O') { - const newScoreO = scorePlayerO + 1; - setScorePlayerO(newScoreO); - if (newScoreO > highestScorePlayerO) { - setHighestScorePlayerO(newScoreO); - } - } - }; - - // Reset the game but keep the scores intact - const resetGame = () => { - setBoard(initialBoard); - setCurrentPlayer('X'); - setWinner(null); - setDraw(false); - }; - - // Handle theme changes - const handleThemeChange = (selectedTheme) => { - setTheme(selectedTheme); - localStorage.setItem('theme', selectedTheme); - applyTheme(selectedTheme); - }; - - const applyTheme = (selectedTheme) => { - if (selectedTheme === 'dark') { - setIsDarkMode(true); - } else if (selectedTheme === 'light') { - setIsDarkMode(false); - } else { - const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; - setIsDarkMode(systemDarkMode); - } - }; - - // Setup initial theme - useEffect(() => { - const savedTheme = localStorage.getItem('theme') || 'system'; - setTheme(savedTheme); - applyTheme(savedTheme); - }, []); - - // Render each cell - const renderCell = (index) => { - const value = board[index]; - return ( -
handleCellClick(index)}> - {value} -
- ); - }; - - // Game Mode selection - const handleBackButton = () => { - setGameMode(null); - resetGame(); - }; - - if (gameMode === null) { - return ( -
-
- - - - -
- - - -
-
-
-

Choose Game Mode

- - -
-
- ); - } - +import React from "react"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import Game from "./Game" +import Signup from "./Signup" +import Login from "./Login" +function App() { return ( -
-
-

Tic Tac Toe

-
- -
-
- X Wins: {scorePlayerX} -
-
- O Wins: {scorePlayerO} -
-
- - - -
- {board.map((cell, index) => renderCell(index))} -
- - {winner && ( -
-

Player {winner} wins!

- -
- )} -
+ + + } /> + } /> + } /> + + + ); -}; +} export default App; diff --git a/paras/src/Game.js b/paras/src/Game.js new file mode 100644 index 0000000..b56cfeb --- /dev/null +++ b/paras/src/Game.js @@ -0,0 +1,192 @@ +import React, { useState, useEffect } from 'react'; +import './App.css'; +import Sparkle from './Sparkle'; // If Sparkle is not used, you can remove this import. +import { Link } from 'react-router-dom'; +const initialBoard = Array(9).fill(null); + +const App = () => { + const [board, setBoard] = useState(initialBoard); + const [currentPlayer, setCurrentPlayer] = useState('X'); + const [winner, setWinner] = useState(null); + const [draw, setDraw] = useState(false); + const [theme, setTheme] = useState('system'); + const [isDarkMode, setIsDarkMode] = useState(false); + const [scorePlayerX, setScorePlayerX] = useState(0); + const [scorePlayerO, setScorePlayerO] = useState(0); + const [highestScorePlayerX, setHighestScorePlayerX] = useState(0); + const [highestScorePlayerO, setHighestScorePlayerO] = useState(0); + const [gameMode, setGameMode] = useState(null); + + // Handle click on a cell + const handleCellClick = (index) => { + if (board[index] || winner || draw) return; + + const newBoard = [...board]; + newBoard[index] = currentPlayer; + setBoard(newBoard); + checkWinner(newBoard, currentPlayer); + setCurrentPlayer(currentPlayer === 'X' ? 'O' : 'X'); + }; + + // Check if there's a winner + const checkWinner = (board, player) => { + const winningCombinations = [ + [0, 1, 2], [3, 4, 5], [6, 7, 8], + [0, 3, 6], [1, 4, 7], [2, 5, 8], + [0, 4, 8], [2, 4, 6] + ]; + for (let combination of winningCombinations) { + const [a, b, c] = combination; + if (board[a] === player && board[b] === player && board[c] === player) { + setWinner(player); + updateScoreAndHighestScore(player); + return; + } + } + if (board.every(cell => cell !== null) && !winner) { + setDraw(true); + } + }; + + // Update score and highest score + const updateScoreAndHighestScore = (player) => { + if (player === 'X') { + const newScoreX = scorePlayerX + 1; + setScorePlayerX(newScoreX); + if (newScoreX > highestScorePlayerX) { + setHighestScorePlayerX(newScoreX); + } + } else if (player === 'O') { + const newScoreO = scorePlayerO + 1; + setScorePlayerO(newScoreO); + if (newScoreO > highestScorePlayerO) { + setHighestScorePlayerO(newScoreO); + } + } + }; + + // Reset the game but keep the scores intact + const resetGame = () => { + setBoard(initialBoard); + setCurrentPlayer('X'); + setWinner(null); + setDraw(false); + }; + + // Handle theme changes + const handleThemeChange = (selectedTheme) => { + setTheme(selectedTheme); + localStorage.setItem('theme', selectedTheme); + applyTheme(selectedTheme); + }; + + const applyTheme = (selectedTheme) => { + if (selectedTheme === 'dark') { + setIsDarkMode(true); + } else if (selectedTheme === 'light') { + setIsDarkMode(false); + } else { + const systemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; + setIsDarkMode(systemDarkMode); + } + }; + + // Setup initial theme + useEffect(() => { + const savedTheme = localStorage.getItem('theme') || 'system'; + setTheme(savedTheme); + applyTheme(savedTheme); + }, []); + + // Render each cell + const renderCell = (index) => { + const value = board[index]; + return ( +
handleCellClick(index)}> + {value} +
+ ); + }; + + // Game Mode selection + const handleBackButton = () => { + setGameMode(null); + resetGame(); + }; + + if (gameMode === null) { + return ( +
+
+ + + + +
+ + + +
+
+
+

Choose Game Mode

+ + +
+
+ ); + } + + return ( +
+
+

Tic Tac Toe

+
+ +
+
+ X Wins: {scorePlayerX} +
+
+ O Wins: {scorePlayerO} +
+
+ + + +
+ {board.map((cell, index) => renderCell(index))} +
+ + {winner && ( +
+

Player {winner} wins!

+ +
+ )} +
+ ); +}; + +export default App; diff --git a/paras/src/Login.css b/paras/src/Login.css new file mode 100644 index 0000000..947f08d --- /dev/null +++ b/paras/src/Login.css @@ -0,0 +1,95 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: Arial, sans-serif; + background-color: #f3f4f6; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +.login-container { + background-color: #fff; + padding: 30px; + border-radius: 10px; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 400px; + text-align: center; +} + +.login-container h2 { + margin-bottom: 20px; + color: #333; +} + +.login-form { + display: flex; + flex-direction: column; +} + +.form-group { + margin-bottom: 15px; + text-align: left; +} + +.form-group label { + font-size: 14px; + color: #666; +} + +.form-group input { + padding: 10px; + width: 100%; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 5px; + margin-top: 5px; +} + +.form-group input:focus { + outline: none; + border-color: #007bff; +} + +.login-btn { + padding: 12px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} + +.login-btn:hover { + background-color: #0056b3; +} + +.error-msg { + color: red; + font-size: 12px; + margin-top: 5px; +} + +.success-msg { + color: green; + font-size: 14px; + margin-bottom: 15px; +} + +.error-input { + border-color: red; +} + +.login-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/paras/src/Login.js b/paras/src/Login.js new file mode 100644 index 0000000..f940faa --- /dev/null +++ b/paras/src/Login.js @@ -0,0 +1,106 @@ +import React, { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import "./Login.css"; + +const Login = () => { + const [formData, setFormData] = useState({ + email: "", + password: "", + }); + + const [errors, setErrors] = useState({}); + const [submitted, setSubmitted] = useState(false); + const navigate = useNavigate(); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + const validate = () => { + let formErrors = {}; + const storedUser = JSON.parse(localStorage.getItem("user")); + + if (!formData.email) { + formErrors.email = "Email is required"; + } else if (!/\S+@\S+\.\S+/.test(formData.email)) { + formErrors.email = "Email address is invalid"; + } + + if (!formData.password) { + formErrors.password = "Password is required"; + } + + if (storedUser) { + if (formData.email !== storedUser.email || formData.password !== storedUser.password) { + formErrors.credentials = "Invalid email or password"; + } + } else { + formErrors.credentials = "No account found. Please sign up."; + } + + return formErrors; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + const formErrors = validate(); + if (Object.keys(formErrors).length === 0) { + setSubmitted(true); + setErrors({}); + // Redirect to the dashboard or home page on successful login + setTimeout(() => { + navigate("/"); + }, 2000); + } else { + setErrors(formErrors); + setSubmitted(false); + } + }; + + return ( +
+

Login

+ {submitted &&

Login successful! Redirecting...

} +
+
+ + + {errors.email &&

{errors.email}

} +
+ +
+ + + {errors.password &&

{errors.password}

} + {errors.credentials &&

{errors.credentials}

} +
+ + +
+

+ Don't have an account?{" "} + Sign Up +

+
+ ); +}; + +export default Login; diff --git a/paras/src/Signup.css b/paras/src/Signup.css new file mode 100644 index 0000000..491428a --- /dev/null +++ b/paras/src/Signup.css @@ -0,0 +1,110 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: Arial, sans-serif; + background-color: #f3f4f6; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +.signup-container { + background-color: #fff; + padding: 30px; + border-radius: 10px; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 400px; + text-align: center; +} + +.signup-container h2 { + margin-bottom: 20px; + color: #333; +} + +.signup-form { + display: flex; + flex-direction: column; +} + +.form-group { + margin-bottom: 15px; + text-align: left; +} + +.form-group label { + font-size: 14px; + color: #666; +} + +.form-group input { + padding: 10px; + width: 100%; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 5px; + margin-top: 5px; +} + +.form-group input:focus { + outline: none; + border-color: #007bff; +} + +.signup-btn { + padding: 12px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} + +.signup-btn:hover { + background-color: #0056b3; +} + +.error-msg { + color: red; + font-size: 12px; + margin-top: 5px; +} + +.success-msg { + color: green; + font-size: 14px; + margin-bottom: 15px; +} + +.error-input { + border-color: red; +} + +.signup-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.redirect-text { + margin-top: 20px; + font-size: 14px; + color: #333; +} + +.redirect-link { + color: #007bff; + text-decoration: none; +} + +.redirect-link:hover { + text-decoration: underline; +} diff --git a/paras/src/Signup.js b/paras/src/Signup.js new file mode 100644 index 0000000..fcbfd6b --- /dev/null +++ b/paras/src/Signup.js @@ -0,0 +1,131 @@ +import React, { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import "./Signup.css"; + +const Signup = () => { + const [formData, setFormData] = useState({ + username: "", + email: "", + password: "", + confirmPassword: "", + }); + + const [errors, setErrors] = useState({}); + const [submitted, setSubmitted] = useState(false); + const navigate = useNavigate(); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + const validate = () => { + let formErrors = {}; + if (!formData.username) formErrors.username = "Username is required"; + if (!formData.email) { + formErrors.email = "Email is required"; + } else if (!/\S+@\S+\.\S+/.test(formData.email)) { + formErrors.email = "Email address is invalid"; + } + if (!formData.password) formErrors.password = "Password is required"; + if (formData.password.length < 6) + formErrors.password = "Password must be at least 6 characters"; + + if (!formData.confirmPassword) { + formErrors.confirmPassword = "Please confirm your password"; + } else if (formData.password !== formData.confirmPassword) { + formErrors.confirmPassword = "Passwords do not match"; + } + + return formErrors; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + const formErrors = validate(); + if (Object.keys(formErrors).length === 0) { + setSubmitted(true); + setErrors({}); + // Save user data to localStorage + localStorage.setItem("user", JSON.stringify(formData)); + // Redirect to login page after successful sign-up + setTimeout(() => { + navigate("/login"); + }, 2000); + } else { + setErrors(formErrors); + setSubmitted(false); + } + }; + + return ( +
+

Sign Up

+ {submitted &&

Sign-up successful! Redirecting to login...

} +
+
+ + + {errors.username &&

{errors.username}

} +
+ +
+ + + {errors.email &&

{errors.email}

} +
+ +
+ + + {errors.password &&

{errors.password}

} +
+ +
+ + + {errors.confirmPassword &&

{errors.confirmPassword}

} +
+ + +
+

+ Already have an account?{" "} + Log In +

+
+ ); +}; + +export default Signup;