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 5371700..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 = () => {
-
+
+
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/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 (
+
+
+
+
+ New Game
+ {won && !keepPlaying && (
+ Keep Playing
+ )}
+
+
+
+
+ {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/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/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
}
}
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 (
+
+ );
+}
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 (
+
+ );
+}