From 479bcbd34baf7d02a8dd54e27dfa36577b3b7838 Mon Sep 17 00:00:00 2001 From: AhmadCSWeb Date: Sun, 11 May 2025 15:43:07 +0500 Subject: [PATCH 1/2] Added updated 1024 game component with improved features and styling --- components/Navbar.jsx | 8 +- components/games/1024/game1024.module.css | 268 ++++++++++++++++++++ components/games/1024/index.js | 283 ++++++++++++++++++++++ gamesList.js | 6 +- 4 files changed, 560 insertions(+), 5 deletions(-) create mode 100644 components/games/1024/game1024.module.css create mode 100644 components/games/1024/index.js diff --git a/components/Navbar.jsx b/components/Navbar.jsx index 5371700..1cae5e8 100644 --- a/components/Navbar.jsx +++ b/components/Navbar.jsx @@ -34,8 +34,8 @@ const Navbar = () => { Whack-a-Mole - - Game 2 + + 1024 Game 3 @@ -58,8 +58,8 @@ const Navbar = () => { Whack-a-Mole - - Game 2 + + 1024 Game 3 diff --git a/components/games/1024/game1024.module.css b/components/games/1024/game1024.module.css new file mode 100644 index 0000000..ad723fd --- /dev/null +++ b/components/games/1024/game1024.module.css @@ -0,0 +1,268 @@ +/* game1024.module.css */ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@700&family=Press+Start+2P&display=swap'); + +.container { + max-width: 500px; + margin: 0 auto; + padding: 20px; + font-family: 'Roboto', sans-serif; + color: #776e65; + user-select: none; + + border-radius: 15px; /* Rounded corners */ + box-shadow: 0 10px 20px rgba(0,0,0,0.2); /* Shadow effect */ + overflow: hidden; /* Prevent content overflow */ +} + +.header { + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} + +.title { + font-size: 3rem; + font-weight: bold; + margin: 0; + color: #776e65; + font-family: 'Press Start 2P', cursive; +} + +.scores { + display: flex; + gap: 10px; +} + +.scoreBox { + background: #bbada0; + padding: 10px 15px; + border-radius: 6px; + text-align: center; + min-width: 60px; +} + +.scoreLabel { + color: #eee4da; + font-size: 0.8rem; + text-transform: uppercase; +} + +.scoreValue { + color: white; + font-size: 1.5rem; + font-weight: bold; +} + +.gameControls { + display: flex; + justify-content: center; + gap: 10px; + margin-bottom: 20px; +} + +.newGameButton, .continueButton { + background: #8f7a66; + color: white; + border: none; + border-radius: 6px; + padding: 10px 20px; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: all 0.2s; +} + +.newGameButton:hover, .continueButton:hover { + background: #9f8b77; +} + +.continueButton { + background: #f67c5f; +} + +.continueButton:hover { + background: #f78c6f; +} + +.gameContainer { + position: relative; + background: #bbada0; + border-radius: 6px; + width: 100%; + padding-bottom: 100%; /* Maintain square aspect ratio */ + touch-action: none; + margin-top: 0px; /* Space from header */ + box-sizing: border-box; /* Include padding in width */ +} + +.gridBackground { + position: absolute; + top: 10px; /* Add some padding */ + left: 10px; + right: 10px; + bottom: 10px; + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(4, 1fr); + gap: 10px; + padding: 10px; + box-sizing: border-box; /* Include padding in dimensions */ +} +.gridCell { + background: rgba(238, 228, 218, 0.35); + border-radius: 3px; +} + +.tilesContainer { + position: absolute; + top: 10px; + left: 10px; + right: 10px; + bottom: 10px; + padding: 10px; + box-sizing: border-box; +} + +.tile { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + width: calc(25% - 15px); + height: calc(25% - 15px); + font-size: 2rem; + font-weight: bold; + border-radius: 3px; + transition: all 0.1s ease-in-out; + z-index: 10; +} + +/* Tile colors */ +.tile-2 { + background: #eee4da; + color: #776e65; +} + +.tile-4 { + background: #ede0c8; + color: #776e65; +} + +.tile-8 { + background: #f2b179; + color: white; +} + +.tile-16 { + background: #f59563; + color: white; +} + +.tile-32 { + background: #f67c5f; + color: white; +} + +.tile-64 { + background: #f65e3b; + color: white; +} + +.tile-128 { + background: #edcf72; + color: white; + font-size: 1.8rem; +} + +.tile-256 { + background: #edcc61; + color: white; + font-size: 1.8rem; +} + +.tile-512 { + background: #edc850; + color: white; + font-size: 1.8rem; +} + +.tile-1024 { + background: #edc53f; + color: white; + font-size: 1.5rem; +} + +.tile-2048 { + background: #edc22e; + color: white; + font-size: 1.5rem; +} + +.gameOverlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(238, 228, 218, 0.73); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border-radius: 6px; + z-index: 100; +} + +.gameOverMessage, .winMessage { + background: rgba(143, 122, 102, 0.8); + color: white; + padding: 20px; + border-radius: 6px; + text-align: center; + max-width: 80%; +} + +.gameOverMessage h2, .winMessage h2 { + margin: 0 0 10px 0; + font-size: 2rem; +} + +.instructions { + text-align: center; + margin-top: 20px; + font-size: 1rem; + line-height: 1.5; +} + +/* Responsive adjustments */ +@media (max-width: 500px) { + .container { + padding: 10px; + margin: 10px; + width: calc(100% - 20px); + } + .title { + font-size: 2rem; + } + + .scoreBox { + min-width: 50px; + padding: 8px 10px; + } + + .scoreValue { + font-size: 1.2rem; + } + + .tile { + font-size: 1.5rem; + } + + .tile-128, .tile-256, .tile-512 { + font-size: 1.3rem; + } + + .tile-1024, .tile-2048 { + font-size: 1rem; + } +} diff --git a/components/games/1024/index.js b/components/games/1024/index.js new file mode 100644 index 0000000..fd0cc97 --- /dev/null +++ b/components/games/1024/index.js @@ -0,0 +1,283 @@ +import { useEffect, useState } from 'react'; +import styles from './game1024.module.css'; + +function initializeGrid(size) { + const grid = Array(size).fill().map(() => Array(size).fill(0)); + return addRandomTile(addRandomTile(grid)); +} + +function addRandomTile(grid) { + const emptyCells = []; + const size = grid.length; + + for (let i = 0; i < size; i++) { + for (let j = 0; j < size; j++) { + if (grid[i][j] === 0) { + emptyCells.push({ i, j }); + } + } + } + + if (emptyCells.length > 0) { + const { i, j } = emptyCells[Math.floor(Math.random() * emptyCells.length)]; + grid[i][j] = Math.random() < 0.9 ? 2 : 4; + } + + return grid; +} + +function moveTiles(grid, direction) { + const size = grid.length; + const newGrid = JSON.parse(JSON.stringify(grid)); + let scoreIncrease = 0; + let moved = false; + + if (direction === 'up' || direction === 'down') { + for (let i = 0; i < size; i++) { + for (let j = i + 1; j < size; j++) { + [newGrid[i][j], newGrid[j][i]] = [newGrid[j][i], newGrid[i][j]]; + } + } + } + + for (let i = 0; i < size; i++) { + let row = newGrid[i].filter(val => val !== 0); + + if (direction === 'right' || direction === 'down') { + row.reverse(); + } + + for (let j = 0; j < row.length - 1; j++) { + if (row[j] === row[j + 1]) { + row[j] *= 2; + scoreIncrease += row[j]; + row[j + 1] = 0; + moved = true; + } + } + + row = row.filter(val => val !== 0); + + while (row.length < size) { + row.push(0); + moved = moved || (direction === 'right' || direction === 'down' ? + newGrid[i][size - row.length] !== 0 : + newGrid[i][row.length - 1] !== 0); + } + + if (direction === 'right' || direction === 'down') { + row.reverse(); + } + + newGrid[i] = row; + } + + if (direction === 'up' || direction === 'down') { + for (let i = 0; i < size; i++) { + for (let j = i + 1; j < size; j++) { + [newGrid[i][j], newGrid[j][i]] = [newGrid[j][i], newGrid[i][j]]; + } + } + } + + return { grid: newGrid, scoreIncrease, moved }; +} + +function checkGameOver(grid) { + const size = grid.length; + + for (let i = 0; i < size; i++) { + for (let j = 0; j < size; j++) { + if (grid[i][j] === 0) { + return false; + } + if (j < size - 1 && grid[i][j] === grid[i][j + 1]) { + return false; + } + if (i < size - 1 && grid[i][j] === grid[i + 1][j]) { + return false; + } + } + } + + return true; +} + +export default function Game1024() { + const gridSize = 4; + const [grid, setGrid] = useState(initializeGrid(gridSize)); + const [score, setScore] = useState(0); + const [bestScore, setBestScore] = useState(0); + const [gameOver, setGameOver] = useState(false); + const [won, setWon] = useState(false); + const [keepPlaying, setKeepPlaying] = useState(false); + const [maxTile, setMaxTile] = useState(2); + + useEffect(() => { + const currentMax = Math.max(...grid.flat()); + setMaxTile(currentMax); + + if (!won && currentMax >= 1024) { + setWon(true); + } + }, [grid]); + + useEffect(() => { + const handleKeyDown = (e) => { + if (gameOver && !keepPlaying) return; + + let direction; + switch (e.key) { + case 'ArrowUp': direction = 'up'; break; + case 'ArrowRight': direction = 'right'; break; + case 'ArrowDown': direction = 'down'; break; + case 'ArrowLeft': direction = 'left'; break; + default: return; + } + + e.preventDefault(); + move(direction); + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [grid, gameOver, keepPlaying]); + + const move = (direction) => { + const { grid: newGrid, scoreIncrease, moved } = moveTiles(grid, direction); + + if (moved) { + const updatedGrid = addRandomTile(newGrid); + const newScore = score + scoreIncrease; + + setGrid(updatedGrid); + setScore(newScore); + setBestScore(prev => Math.max(prev, newScore)); + + if (checkGameOver(updatedGrid)) { + setGameOver(true); + } + } + }; + + const resetGame = () => { + setGrid(initializeGrid(gridSize)); + setScore(0); + setGameOver(false); + setWon(false); + setKeepPlaying(false); + setMaxTile(2); + }; + + const continueGame = () => { + setKeepPlaying(true); + }; + + let touchStartX = 0; + let touchStartY = 0; + + const handleTouchStart = (e) => { + touchStartX = e.touches[0].clientX; + touchStartY = e.touches[0].clientY; + }; + + const handleTouchEnd = (e) => { + if (gameOver && !keepPlaying) return; + + const touchEndX = e.changedTouches[0].clientX; + const touchEndY = e.changedTouches[0].clientY; + const dx = touchEndX - touchStartX; + const dy = touchEndY - touchStartY; + + if (Math.abs(dx) > Math.abs(dy)) { + if (dx > 0) move('right'); + else move('left'); + } else { + if (dy > 0) move('down'); + else move('up'); + } + }; + + return ( +
+
+

1024

+
+
+
SCORE
+
{score}
+
+
+
BEST
+
{bestScore}
+
+
+
MAX TILE
+
{maxTile}
+
+
+
+ +
+ + {won && !keepPlaying && ( + + )} +
+ +
+
+ {Array(gridSize * gridSize).fill().map((_, index) => ( +
+ ))} +
+ +
+ {grid.map((row, rowIndex) => ( + row.map((value, colIndex) => ( + value !== 0 && ( +
+ {value} +
+ ) + )) + ))} +
+ + {(gameOver || (won && !keepPlaying)) && ( +
+ {won && !keepPlaying ? ( +
+

You Win!

+

Reached {maxTile} tile!

+ {maxTile > 1024 &&

Amazing! You got beyond 1024!

} +
+ ) : ( +
+

Game Over!

+

Max Tile: {maxTile}

+
+ )} +
+ )} +
+ +
+

Join the numbers and get to the 1024 tile!

+

Use arrow keys or swipe to move the tiles.

+
+ +
+ ); +} \ No newline at end of file diff --git a/gamesList.js b/gamesList.js index b38b40a..79deab8 100644 --- a/gamesList.js +++ b/gamesList.js @@ -1,6 +1,6 @@ import TicTacToe from "./components/games/tictactoe"; import Whack_A_Mole from "./components/games/whack-a-mole"; - +import game3 from "./components/games/1024"; const games = { "whack-a-mole": { component: Whack_A_Mole @@ -8,6 +8,10 @@ const games = { "tictactoe": { component: TicTacToe + }, + + "1024": { + component: game3 } } From 3f90d574931e49f59e6d60a7c1bdeaf34ca50ef7 Mon Sep 17 00:00:00 2001 From: AhmadCSWeb Date: Sun, 11 May 2025 17:36:06 +0500 Subject: [PATCH 2/2] Complete 1024 game implementation with comments and improved UI --- README.md | 4 + components/Navbar.jsx | 111 +++++++++++++++--- components/auth/auth-form.js | 74 ++++++++++++ components/auth/auth-form.module.css | 72 ++++++++++++ env.local | 1 + lib/mongodb.js | 25 ++++ package-lock.json | 163 +++++++++++++++++++++++++++ package.json | 1 + pages/(auth)/login/index.js | 9 -- pages/(auth)/signup/index.js | 9 -- pages/_app.js | 3 + pages/api/auth/login.js | 30 +++++ pages/api/auth/signup.js | 31 +++++ pages/api/protected.js | 15 +++ pages/auth/login.jsx | 69 ++++++++++++ pages/auth/signup.jsx | 68 +++++++++++ 16 files changed, 649 insertions(+), 36 deletions(-) create mode 100644 components/auth/auth-form.js create mode 100644 components/auth/auth-form.module.css create mode 100644 env.local create mode 100644 lib/mongodb.js delete mode 100644 pages/(auth)/login/index.js delete mode 100644 pages/(auth)/signup/index.js create mode 100644 pages/api/auth/login.js create mode 100644 pages/api/auth/signup.js create mode 100644 pages/api/protected.js create mode 100644 pages/auth/login.jsx create mode 100644 pages/auth/signup.jsx diff --git a/README.md b/README.md index 05edc32..8079a18 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ - A web-app for playing video games. - The website will feature arcade games, user accounts, leaderboards, user comments (CRUD operations), and an admin panel. - The games may be few and super-simple (to keep Web Dev. the primary focus of the project). Feedback appreciated! +### Games +- Tictactoe +- Whack-a-mole +- 1024 diff --git a/components/Navbar.jsx b/components/Navbar.jsx index 1cae5e8..becc9bf 100644 --- a/components/Navbar.jsx +++ b/components/Navbar.jsx @@ -1,6 +1,6 @@ // components/Navbar.js -import { useState } from 'react'; -import Link from 'next/link'; +import { useState } from "react"; +import Link from "next/link"; const Navbar = () => { const [isOpen, setIsOpen] = useState(false); @@ -14,60 +14,135 @@ const Navbar = () => {

GameSpace

-
{/* Desktop Menu */}
- + Tic Tac Toe - + Whack-a-Mole +<<<<<<< HEAD 1024 +======= + + Game 2 +>>>>>>> 5dcd9aaee1c4c68702f7eb87312da3141a8d2973 - + Game 3
- + Sign Up - + Login
{/* Mobile Menu */} -
- +
+ Tic Tac Toe - + Whack-a-Mole +<<<<<<< HEAD 1024 +======= + + Game 2 +>>>>>>> 5dcd9aaee1c4c68702f7eb87312da3141a8d2973 - + Game 3 - + Sign Up - + Login
diff --git a/components/auth/auth-form.js b/components/auth/auth-form.js new file mode 100644 index 0000000..7904fca --- /dev/null +++ b/components/auth/auth-form.js @@ -0,0 +1,74 @@ +import { useState,useRef } from 'react'; +import classes from './auth-form.module.css'; +import {signIn} from 'next-auth/react'; +import { useRouter } from 'next/router'; + + +function createUser(e,p){ + fetch('/api/auth/signup',{ + method:'POST', + body:JSON.stringify({email:e,password:p}), + headers:{ + 'Content-Type':'application/json' + } + }).then(res=>res.json()).then(d=>console.log(d)); +} + +function AuthForm() { + const router=useRouter(); + const eref=useRef(); + const pref=useRef(); + const [isLogin, setIsLogin] = useState(true); + + function switchAuthModeHandler() { + setIsLogin((prevState) => !prevState); + } + + async function submitHandler(event){ + event.preventDefault(); + const email=eref.current.value; + const pass=pref.current.value; + if(isLogin) + { + //log user in + const result= await signIn('credentials',{redirect:false,email:email,password:pass}) + console.log(result) + if(!result.error) + { + router.replace('/profile') + } + } + else + { + //send request to create a new user + createUser(email,pass); + } + } + return ( +
+

{isLogin ? 'Login' : 'Sign Up'}

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ ); +} + +export default AuthForm; \ No newline at end of file diff --git a/components/auth/auth-form.module.css b/components/auth/auth-form.module.css new file mode 100644 index 0000000..5942ccb --- /dev/null +++ b/components/auth/auth-form.module.css @@ -0,0 +1,72 @@ +.auth { + margin: 3rem auto; + width: 95%; + max-width: 25rem; + border-radius: 6px; + background-color: #38015c; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); + padding: 1rem; + text-align: center; + } + + .auth h1 { + text-align: center; + color: white; + } + + .control { + margin-bottom: 0.5rem; + } + + .control label { + display: block; + color: white; + font-weight: bold; + margin-bottom: 0.5rem; + } + + .control input { + font: inherit; + background-color: #f1e1fc; + color: #38015c; + border-radius: 4px; + border: 1px solid white; + width: 100%; + text-align: left; + padding: 0.25rem; + } + + .actions { + margin-top: 1.5rem; + display: flex; + flex-direction: column; + align-items: center; + } + + .actions button { + cursor: pointer; + font: inherit; + color: white; + background-color: #9f5ccc; + border: 1px solid #9f5ccc; + border-radius: 4px; + padding: 0.5rem 2.5rem; + } + + .actions button:hover { + background-color: #873abb; + border-color: #873abb; + } + + .actions .toggle { + margin-top: 1rem; + background-color: transparent; + color: #9f5ccc; + border: none; + padding: 0.15rem 1.5rem; + } + + .actions .toggle:hover { + background-color: transparent; + color: #ae82cc; + } \ No newline at end of file diff --git a/env.local b/env.local new file mode 100644 index 0000000..bed0765 --- /dev/null +++ b/env.local @@ -0,0 +1 @@ +DATABASE_URL=mongodb+srv://GameSpaceDev:GameSpaceDev1234@gamespacecluster.79wzx7g.mongodb.net/?retryWrites=true&w=majority&appName=GameSpaceCluster \ No newline at end of file diff --git a/lib/mongodb.js b/lib/mongodb.js new file mode 100644 index 0000000..f755c4b --- /dev/null +++ b/lib/mongodb.js @@ -0,0 +1,25 @@ +// lib/mongodb.js +import { MongoClient } from "mongodb"; + +const uri = process.env.DATABASE_URL; +const options = {}; + +let client; +let clientPromise; + +if (!uri) { + throw new Error("Please add your DATABASE_URL to .env.local"); +} + +if (process.env.NODE_ENV === "development") { + if (!global._mongoClientPromise) { + client = new MongoClient(uri, options); + global._mongoClientPromise = client.connect(); + } + clientPromise = global._mongoClientPromise; +} else { + client = new MongoClient(uri, options); + clientPromise = client.connect(); +} + +export default clientPromise; diff --git a/package-lock.json b/package-lock.json index 1c8a813..7b082ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "game_space": "file:", "mongodb": "^6.16.0", "next": "15.3.1", + "next-auth": "^4.24.11", "react": "^19.1.0", "react-dom": "^19.1.0" }, @@ -33,6 +34,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", @@ -563,6 +573,15 @@ "node": ">= 10" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -1054,6 +1073,15 @@ "node": ">= 0.8" } }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1306,6 +1334,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/lightningcss": { "version": "1.29.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", @@ -1545,6 +1582,18 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1709,6 +1758,38 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.11", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", + "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.2", + "next": "^12.2.5 || ^13 || ^14 || ^15", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -1737,6 +1818,45 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/oidc-token-hash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz", + "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1772,6 +1892,34 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.26.6", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.6.tgz", + "integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -1962,6 +2110,15 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -1983,6 +2140,12 @@ "engines": { "node": ">=18" } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" } } } diff --git a/package.json b/package.json index 351d038..f7b8ad4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "game_space": "file:", "mongodb": "^6.16.0", "next": "15.3.1", + "next-auth": "^4.24.11", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/pages/(auth)/login/index.js b/pages/(auth)/login/index.js deleted file mode 100644 index 45e3e9d..0000000 --- a/pages/(auth)/login/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -const Login = () => { - return ( -
Login
- ) -} - -export default Login \ No newline at end of file diff --git a/pages/(auth)/signup/index.js b/pages/(auth)/signup/index.js deleted file mode 100644 index 203e7a9..0000000 --- a/pages/(auth)/signup/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -const Signup = () => { - return ( -
Signup
- ) -} - -export default Signup \ No newline at end of file diff --git a/pages/_app.js b/pages/_app.js index 20a95c3..1ca7ce6 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,11 +1,14 @@ + import Layout from "@/components/layout"; import "@/styles/globals.css"; export default function App({ Component, pageProps }) { return ( + + ); } diff --git a/pages/api/auth/login.js b/pages/api/auth/login.js new file mode 100644 index 0000000..b9a7ebb --- /dev/null +++ b/pages/api/auth/login.js @@ -0,0 +1,30 @@ +import { MongoClient } from "mongodb"; + +export default async function handler(req, res) { + if (req.method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }); + } + + const { email, password } = req.body; + + if (!email || !password) { + return res.status(400).json({ message: "Email and password are required" }); + } + + try { + const client = await MongoClient.connect("mongodb+srv://GameSpaceDev:GameSpaceDev1234@gamespacecluster.79wzx7g.mongodb.net/?retryWrites=true&w=majority&appName=GameSpaceCluster"); + const db = client.db("GameSpaceDB"); + + const user = await db.collection("Users").findOne({ email }); + + if (!user || user.password !== password) { + return res.status(401).json({ message: "Invalid email or password" }); + } + + // For simplicity, return a dummy token (in real apps, use JWT or sessions) + return res.status(200).json({ message: "Login successful", token: "dummy-token" }); + } catch (error) { + console.error(error); + return res.status(500).json({ message: "Internal server error" }); + } +} diff --git a/pages/api/auth/signup.js b/pages/api/auth/signup.js new file mode 100644 index 0000000..729c1de --- /dev/null +++ b/pages/api/auth/signup.js @@ -0,0 +1,31 @@ +import { MongoClient } from "mongodb"; + +export default async function handler(req, res) { + if (req.method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }); + } + + const { email, password } = req.body; + + if (!email || !password) { + return res.status(400).json({ message: "Email and password are required" }); + } + + try { + const client = await MongoClient.connect("mongodb+srv://GameSpaceDev:GameSpaceDev1234@gamespacecluster.79wzx7g.mongodb.net/?retryWrites=true&w=majority&appName=GameSpaceCluster"); + const db = client.db("GameSpaceDB"); + + const existingUser = await db.collection("Users").findOne({ email }); + if (existingUser) { + return res.status(409).json({ message: "User already exists" }); + } + + // Save user (plain password for now) + await db.collection("Users").insertOne({ email, password }); + + return res.status(201).json({ message: "User created successfully" }); + } catch (error) { + console.error(error); + return res.status(500).json({ message: "Internal server error" }); + } +} diff --git a/pages/api/protected.js b/pages/api/protected.js new file mode 100644 index 0000000..84db94a --- /dev/null +++ b/pages/api/protected.js @@ -0,0 +1,15 @@ +import { useEffect } from "react"; +import { useRouter } from "next/router"; + +export default function Protected() { + const router = useRouter(); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + router.push("/auth/login"); + } + }, [router]); + + return

Protected content: You are logged in!

; +} diff --git a/pages/auth/login.jsx b/pages/auth/login.jsx new file mode 100644 index 0000000..ae91dff --- /dev/null +++ b/pages/auth/login.jsx @@ -0,0 +1,69 @@ +import { useState } from "react"; +import { useRouter } from "next/router"; + +export default function Login() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const router = useRouter(); + + async function handleSubmit(e) { + e.preventDefault(); + setError(""); + + const res = await fetch("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + + const data = await res.json(); + + if (res.ok) { + localStorage.setItem("token", data.token); + router.push("/"); + } else { + setError(data.message || "Login failed"); + } + } + + return ( +
+
+

Welcome Back Gamespacer

+ {error &&

{error}

} + + + +
+
+ ); +} diff --git a/pages/auth/signup.jsx b/pages/auth/signup.jsx new file mode 100644 index 0000000..0c210b2 --- /dev/null +++ b/pages/auth/signup.jsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { useRouter } from "next/router"; + +export default function Signup() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const router = useRouter(); + + async function handleSubmit(e) { + e.preventDefault(); + setError(""); + + const res = await fetch("/api/auth/signup", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + + const data = await res.json(); + + if (res.ok) { + router.push("/auth/login"); + } else { + setError(data.message || "Signup failed"); + } + } + + return ( +
+
+

Welcome to GameSpace

+ {error &&

{error}

} + + + +
+
+ ); +}