bug fixes

This commit is contained in:
Moon Patel
2023-07-25 03:01:45 +05:30
parent f86846004f
commit bd13e2b129
33 changed files with 1582 additions and 865 deletions
+2 -2
View File
@@ -44,8 +44,8 @@ app.use((error, req, res, next) => {
const status = error.status || 500;
console.log(error);
res.status(status).json({
userMessage: "Something went wrong",
devMessage: error?.message || "Internal server error",
message: "Something went wrong",
description: error?.message || "Internal server error",
});
});
+6 -6
View File
@@ -67,7 +67,7 @@ router.post("/signup", async (req, res, next) => {
});
} catch (err) {
if (err instanceof ZodError) {
return res.status(400).json({ userMessage: "Invalid data submitted", devMessage: "Invalid schema" });
return res.status(400).json({ message: "Invalid data submitted", description: "Invalid schema" });
}
next(err);
}
@@ -84,16 +84,16 @@ router.post("/login", async (req, res, next) => {
user = await User.findOne({ username });
if (!user)
return res.status(404).json({
userMessage: "User does not exist",
devMessage: "'username' not found in db",
message: "User does not exist",
description: "'username' not found in db",
});
const pwIsValid = await isValidPassword(password, user.password_hash);
if (!pwIsValid) {
return res.status(401).json({
success: false,
userMessage: "Invalid credentials",
devMessage: "Invalid credentials",
message: "Invalid credentials",
description: "Invalid credentials",
});
}
@@ -104,7 +104,7 @@ router.post("/login", async (req, res, next) => {
.json({ token, user: { id: user.id, username: user.username, email: user.email }, success: true });
} catch (error) {
if (error instanceof ZodError) {
return res.status(401).json({ userMessage: "Invalid Credentials", devMessage: "Invalid schema" });
return res.status(401).json({ message: "Invalid Credentials", description: "Invalid schema" });
}
next(error);
}
+8 -5
View File
@@ -4,20 +4,22 @@ const { createRoom } = require("../socket");
const { checkAuth } = require("../util/auth");
const { Challenge } = require("../models/challenge");
async function fetchChallenge({ challenger, challenged }) {}
// async function fetchChallenge({ challenger, challenged }) {}
// rooms can only be created through HTTP requests and destroyed only by socket.io server
// and vice versa is not true
router.post("/create", checkAuth, async (req, res, next) => {
router.post("/", checkAuth, async (req, res, next) => {
console.log(req.body);
// challenger and challenged are username, color is the color played by challenger, timeLimit is the timeLimit for one player
const { challenger, challenged, color, timeLimit } = req.body;
let challenge = await Challenge.findOne({ challenger });
// a user can create only one challenge at a time
if (challenge) {
return res.status(405).json({ success: false, error: { message: "Cannot create new challenge" } });
return res.status(405).json({
message: "Cannot create new challenge",
description: "User already created a challenge",
});
}
// get email of the challenged person
@@ -45,7 +47,8 @@ router.post("/create", checkAuth, async (req, res, next) => {
// `Challenge from ${challenger}`,
// `To accept the challenge follow the link: http://192.168.136.99:5173/game/challenges/${challenged}/${roomID} \n login through: http://192.168.136.99:5173/login \n roomid:${roomID}`
// );
res.json({ roomID });
console.log(roomID);
res.status(201).json({ roomID });
});
module.exports = router;
+186 -135
View File
@@ -5,177 +5,228 @@ const { User } = require("../models/user");
const { checkAuth } = require("../util/auth");
const { catchAsync } = require("../util/errors");
// TO BE TESTED
// attaches user data to the route object
router.use(
catchAsync(async (req, res, next) => {
let userID = req.url.split("/")[1];
if (userID?.length !== 24)
return res.status(404).json({ userMessage: "User not found", devMessage: "Invalid user ID" });
let userData = await User.findById(userID);
if (userData) {
req.userData = userData;
next();
} else {
res.status(404).json({
success: false,
userMessage: "User not found",
devMessage: "User ID does not exists",
});
}
})
);
// TO BE TESTED
// get user details
router.get(
"/:userid",
catchAsync(async (req, res, next) => {
let userData = req.userData;
let { id, username, email, fname, lname, country, location } = userData;
let friends = await userData.getFriends();
let games = await userData.getGames();
// get the logged in user details
router.get("/", checkAuth, async (req, res, next) => {
try {
let { userId } = req;
const user = await User.findById(userId);
let { id, username, email, fname, lname, country, location } = user;
let friends = await user.getFriends();
let games = await user.getGames();
return res.status(200).json({ id, username, email, friends, fname, lname, country, location, games });
})
);
} catch (err) {
next(err);
}
});
router.get("/friends", checkAuth, async (req, res, next) => {
try {
let { userId } = req;
let user = await User.findById(userId);
let friends = await user.getFriends();
return res.status(200).json(friends);
} catch (err) {
next(err);
}
});
router.get("/challenges", checkAuth, async (req, res, next) => {
try {
let { userId } = req;
let user = await User.findById(userId);
let challenges = await Challenge.find({ challenged: user.username });
challenges = challenges.map((challenge) => {
let { id, challenged, challenger, color, roomID, timeLimit } = challenge;
return { id, challenged, challenger, color, roomID, timeLimit };
});
console.log(challenges);
res.status(200).json(challenges);
} catch (err) {
next(err);
}
});
// TODO
// get history of games played
router.get("/games", checkAuth, async (req, res, next) => {
try {
let { userId } = req;
const user = await User.findById(userId);
let games = await user.getGames();
if (!games) games = [];
return res.status(200).json(gamesData);
} catch (err) {
next(err);
}
});
// TODO
router.get("/games/:gameid", checkAuth, async (req, res, next) => {
try {
} catch (err) {
next(err);
}
});
// TODO
router.get("");
// TO BE TESTED
// update user details
router.patch(
"/:userid",
checkAuth,
catchAsync(async (req, res, next) => {
let { userid } = req.params;
router.patch("/", checkAuth, async (req, res, next) => {
try {
let { userId } = req;
let updatedData = req.body;
console.log("Updated data: ", updatedData);
// console.log(updatedData)
await User.findByIdAndUpdate(userid, { ...updatedData });
let { id, username, email, fname, lname, location, country, fullName } = await User.findById(userid);
// console.log(req.userData);
console.log({ id, username, email, fname, lname, location, country, fullName });
return res.json({ success: true, user: { id, username, email, fname, lname, location, country, fullName } });
})
);
await User.findByIdAndUpdate(userId, { ...updatedData });
let { id, username, email, fname, lname, location, country, fullName } = await User.findById(userId);
return res.status(200).json({ user: { id, username, email, fname, lname, location, country, fullName } });
} catch (err) {
next(err);
}
});
// TO BE TESTED
// delete the user
router.delete(
"/:userid",
checkAuth,
catchAsync(async (req, res, next) => {
let { userData } = req;
await userData.deleteOne();
})
);
router.delete("/", checkAuth, async (req, res, next) => {
try {
let { userId } = req;
let user = await User.findById(userId);
await user.deleteOne();
return res.status(204).json({ message: "Account deleted succesfully" });
} catch (err) {
next(err);
}
});
// get friends of the user
router.get(
"/:userid/friends",
checkAuth,
catchAsync(async (req, res, next) => {
const friends = await req.userData.getFriends();
return res.json({ success: true, friends });
})
);
// TO BE TESTED
// TODO: add some logic to notify the challenger if the challenged user declines the challenge
// accept or decline a challenge
// challengeID here refers to the roomID associated with the challenge
router.delete("/challenges/:challengeID", checkAuth, async (req, res, next) => {
try {
let { challengeID } = req.params;
let challenge = await Challenge.findById(challengeID);
if (!challenge)
return res
.status(404)
.json({ message: "Challenge not found", description: "Challenge ID does not exists" });
await challenge.deleteOne();
return res.status(200).json({});
} catch (err) {
next(err);
}
});
// TO BE TESTED
// add a friend
router.post(
"/:userid/friends/:friendusername",
checkAuth,
catchAsync(async (req, res, next) => {
let { friendusername } = req.params;
if (req.userData.username === friendusername)
res.json({ success: false, error: { message: "Cannot add yourself as friend" } });
let friendData = await User.findOne({ username: friendusername });
if (friendData) {
if (friendData.friends.includes(req.userData._id)) {
res.json({ success: false, error: { message: "User is already added as a friend" } });
} else {
friendData.friends.push(req.userData._id);
await friendData.save();
req.userData.friends.push(friendData._id);
await req.userData.save();
res.json({ success: true });
}
router.post("/friends/:friendusername", checkAuth, async (req, res, next) => {
let { friendusername } = req.params;
let { userId } = req;
const user = await User.findById(userId);
if (req.user.username === friendusername)
res.json({
error: { description: "Cannot add yourself as friend", message: "Cannot add this user as friends" },
});
let friendData = await User.findOne({ username: friendusername });
if (friendData) {
if (friendData.friends.includes(req.user._id)) {
res.json({
error: {
message: "User is already added as a friend",
description: "User is already added as a friend",
},
});
} else {
res.status(404).json({ success: false, error: { message: "username does not exist" } });
friendData.friends.push(req.user._id);
await friendData.save();
req.user.friends.push(friendData._id);
await req.user.save();
res.status(204).json(null);
}
})
);
} else {
res.status(404).json({
error: { message: "User not found", description: "username not found in DB" },
});
}
});
// TODO
// remove a user from friends list
router.delete(
"/:userid/friends/:friendid",
"/friends/:friendid",
checkAuth,
catchAsync(async (req, res, next) => {
const { friendid } = req.params;
const { userData } = req;
const { userId } = req;
const user = await User.findById(userId);
// Find the friend user to be removed
const friendData = await User.findById(friendid);
if (!friendData) {
return res.status(404).json({ success: false, error: { message: "Friend user not found" } });
return res.status(404).json({ error: { message: "Friend user not found" } });
}
// Remove the friend from the user's friends list
const friendIndex = userData.friends.indexOf(friendData._id);
const friendIndex = user.friends.indexOf(friendData._id);
if (friendIndex === -1) {
return res
.status(400)
.json({ success: false, error: { message: "Friend user not found in the friends list" } });
return res.status(400).json({ error: { message: "Friend user not found in the friends list" } });
}
userData.friends.splice(friendIndex, 1);
await userData.save();
user.friends.splice(friendIndex, 1);
await user.save();
// Remove the user from the friend's friends list
const userIndex = friendData.friends.indexOf(userData._id);
const userIndex = friendData.friends.indexOf(user._id);
if (userIndex === -1) {
return res
.status(400)
.json({ success: false, error: { message: "User not found in the friend's friends list" } });
return res.status(400).json({ error: { message: "User not found in the friend's friends list" } });
}
friendData.friends.splice(userIndex, 1);
await friendData.save();
return res.json({ success: true });
return res.json({});
})
);
// get current challenges of the user
router.get(
"/:userid/challenges",
checkAuth,
catchAsync(async (req, res, next) => {
let { userData } = req;
let challenges = await Challenge.find({ challenged: userData.username });
if (!challenges) challenges = [];
console.log("Challenges to", userData.username, challenges);
res.json({ success: true, challenges: challenges });
})
);
// =============================================================
// TO BE TESTED
// accept or decline a challenge
// challengeID here refers to the roomID associated with the challenge
router.delete(
"/:userid/challenges/:challengeID",
checkAuth,
catchAsync(async (req, res) => {
let challengeResponse = req.query.response;
let { challengeID } = req.params;
if (challengeResponse === "accept") {
let { deletedCount } = await Challenge.deleteOne({ roomID: challengeID });
return res.json({ success: true });
} else if (challengeResponse === "decline") {
let { deletedCount } = await Challenge.deleteOne({ roomID: challengeID });
return res.json({ success: true });
} else {
res.json({ success: false, error: { message: "Invalid query parameter" } });
}
})
);
// get user details
router.get("/:userid", async (req, res, next) => {
try {
let userId = req.params.userid;
const user = await User.findById(userId);
let { id, username, email, fname, lname, country, location } = user;
let friends = await user.getFriends();
let games = await user.getGames();
return res.status(200).json({ id, username, email, friends, fname, lname, country, location, games });
} catch (err) {
next(err);
}
});
// get friends of the user
router.get("/:userid/friends", async (req, res, next) => {
try {
const user = await User.findById(req.params.userid);
const friends = await user.getFriends();
return res.json({ friends });
} catch (err) {
next(err);
}
});
// get current challenges of the user
router.get("/:userid/challenges", checkAuth, async (req, res, next) => {
try {
let { userId } = req;
const user = await user.findById(userId);
let challenges = await Challenge.find({ challenged: user.username });
if (!challenges) challenges = [];
console.log("Challenges to", user.username, challenges);
res.json({ challenges: challenges });
} catch (err) {
next(err);
}
});
// TODO
// get history of games played
@@ -183,10 +234,10 @@ router.get(
"/:userid/games",
checkAuth,
catchAsync(async (req, res, next) => {
const userData = await User.findOne();
let gamesData = await userData.getGames();
const user = await User.findOne();
let gamesData = await user.getGames();
if (!gamesData) gamesData = [];
return res.status(200).json({ success: true, data: gamesData });
return res.status(200).json({ data: gamesData });
})
);
@@ -195,7 +246,7 @@ router.get(
router.post("/:userid/game", checkAuth, async (req, res, next) => {
const gameData = req.body;
const gameDoc = await Game.create(gameData);
return res.json({ success: true, data: gameDoc });
return res.json({ data: gameDoc });
});
// TODO
@@ -207,9 +258,9 @@ router.get(
const { gameid } = req.params;
const gameData = await Game.findById(gameid);
if (gameData) {
return res.status(200).json({ success: true, data: gameData });
return res.status(200).json({ data: gameData });
} else {
return res.status(404).json({ success: false, error: { message: "Game not found" } });
return res.status(404).json({ error: { message: "Game not found" } });
}
})
);
-1
View File
@@ -6,7 +6,6 @@ const { Game } = require("./models/game");
const {
CHESS_MOVE,
CHESS_OPPONENT_MOVE,
CONNECTION,
JOIN_ROOM,
JOIN_ROOM_ERROR,
JOIN_ROOM_SUCCESS,
+4 -3
View File
@@ -52,14 +52,15 @@ function checkAuthMiddleware(req, res, next) {
}
let authToken = req.cookies["auth-token"];
if (!authToken) {
return res.status(401).json({ userMessage: "Not authenticated", devMessage: "Auth token not found" });
return res.status(401).json({ message: "Not authenticated", description: "Auth token not found" });
}
try {
const validatedToken = validateJSONToken(authToken);
req.userid = validatedToken;
req.userId = validatedToken.id;
req.isAuthenticated = true;
} catch (error) {
console.log("NOT AUTH. TOKEN INVALID.");
return res.status(401).json({ userMessage: "Not authenticated", devMessage: "Invalid auth token" });
return res.status(401).json({ message: "Not authenticated", description: "Invalid auth token" });
}
next();
}
+5
View File
@@ -0,0 +1,5 @@
node_modules/
dist/
.prettierrc.js
.eslintrc.js
env.d.ts
+36 -14
View File
@@ -1,15 +1,37 @@
module.exports = {
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
},
}
extends: [
// By extending from a plugin config, we can get recommended rules without having to add them manually.
'eslint:recommended',
'plugin:react/recommended',
'plugin:import/recommended',
'plugin:jsx-a11y/recommended',
'plugin:@typescript-eslint/recommended',
// This disables the formatting rules in ESLint that Prettier is going to be responsible for handling.
// Make sure it's always the last config, so it gets the chance to override other configs.
'eslint-config-prettier',
],
settings: {
react: {
// Tells eslint-plugin-react to automatically detect the version of React to use.
version: 'detect',
},
// Tells eslint how to resolve imports
'import/resolver': {
node: {
paths: ['src'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
rules: {
// Add your own rules here to override ones from the extended configs.
"react/react-in-jsx-scope": "off",
"react/jsx-uses-react": "off",
"no-console": "off",
},
"env":{
"browser":true,
"node":true
}
};
+1169 -510
View File
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -17,8 +17,16 @@
"@mantine/hooks": "^6.0.14",
"@radix-ui/react-icons": "^1.3.0",
"@tabler/icons-react": "^2.23.0",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"chess.js": "^1.0.0-beta.6",
"eslint": "^8.45.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.33.0",
"js-cookie": "^3.0.5",
"prettier": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.2",
@@ -30,8 +38,6 @@
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.38.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"vite": "^4.3.9"
+3 -6
View File
@@ -1,9 +1,7 @@
import './App.css'
import { createBrowserRouter, redirect, RouterProvider } from 'react-router-dom'
import Home from './pages/Home'
import MainLayout from './layout/MainLayout'
import Settings from './pages/Settings/Settings'
import Profile from './pages/Settings/Profile'
import Friends from './pages/Settings/Friends'
import Password from './pages/Settings/Password'
import Themes from './pages/Settings/Themes'
@@ -11,12 +9,11 @@ import PlayLayout from './pages/Play/Layout'
import PlayFriend from './pages/Play/PlayFriend'
import Play from './pages/Play/Play'
import AuthenticationPage, { loginAction, signupAction } from './pages/Authentication/Authentication'
import { getAuthToken, getUserData } from '../utils/auth'
import { logoutAction } from './components/Logout'
import { getAuthToken, getUserData } from './utils/auth'
import ChallengeFriend, { playFriendAction } from './pages/Play/ChallengeFriend'
import ChessGame from './pages/Chess/ChessGame'
import ChessGameContextProvider from './context/chess-game-context'
import { action as profileAction } from './pages/Settings/Profile'
import Profile, { action as profileAction } from './pages/Settings/Profile'
const router = createBrowserRouter([{
path: '/',
@@ -58,7 +55,7 @@ const router = createBrowserRouter([{
}, {
path: '/signup', element: <AuthenticationPage isLogin={false} />, action: signupAction, loader: () => { if (getAuthToken()) return redirect('/signup'); else return null; }
}, {
path: '/logout', loader: () => { getAuthToken() || redirect('/home') }, action: logoutAction
path: '/logout', loader: () => { getAuthToken() || redirect('/home') }
}]);
function App() {
+11 -6
View File
@@ -1,7 +1,7 @@
import React, { useContext, useState } from 'react'
import React, { useContext } from 'react'
import Piece from './Piece';
import { socket } from '../socket';
import { Box, Flex, Modal } from '@mantine/core';
import { Box, Flex } from '@mantine/core';
import { useDroppable } from '@dnd-kit/core'
import { ChessGameContext } from '../context/chess-game-context';
import { SOCKET_EVENTS } from '../constants';
@@ -9,7 +9,7 @@ const { CHESS_MOVE } = SOCKET_EVENTS
const Cell = ({ cell }) => {
let roomID = localStorage.getItem('roomID');
let { square, type, color } = cell;
let { square, type } = cell;
const { getSquareColor, isSquareMarked, handleSquareClick } = useContext(ChessGameContext)
const { isOver, setNodeRef } = useDroppable({ id: square });
let squareColor = getSquareColor(square);
@@ -24,11 +24,16 @@ const Cell = ({ cell }) => {
});
}
let content = marked && !type ? <Mark /> : <Piece cell={cell} />;
let content = null;
if (marked) {
content = <Mark />
} else if (type) {
content = <Piece cell={cell} />
}
return (
<Flex ref={setNodeRef} style={{ aspectRatio: '1/1', position: 'relative' }} sx={theme => {
let color = theme.colors.lime
<Flex ref={setNodeRef} style={{ aspectRatio: '1/1' }} sx={theme => {
let color = theme.colors.lime;
return { backgroundColor: squareColor === 'b' ? color[8] : color[1], filter: 'saturate(0.5)' }
}} onClick={handleClick} bg={squareColor === 'w' ? "white" : "gray"} >
{
+62 -69
View File
@@ -1,88 +1,35 @@
import { Button, Group, Stack, Text, Title } from '@mantine/core';
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom';
import { getAuthToken, getUserData } from '../../utils/auth'
const Challenges = () => {
const navigate = useNavigate();
const [challenges, setChallenges] = useState([]);
const { id: userid } = getUserData();
const [error, setError] = useState(null);
console.log(challenges)
useEffect(() => {
const abortController = new AbortController();
let response = null;
if (error) return;
const fetchData = async () => {
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/${userid}/challenges`;
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/challenges`;
try {
response = await fetch(url, {
signal: abortController.signal, headers: {
'Authorization': `Bearer ${getAuthToken()}`
}
})
const response = await fetch(url, {
credentials: 'include'
});
const data = await response.json();
if (data.success) {
setChallenges(data.challenges);
if (response.ok) {
setChallenges(data);
} else {
throw data.error;
setError('Cannot')
}
} catch (error) {
if (error.message === 'The user aborted a request.');
else {
console.log('Error fetching data');
throw error;
}
console.log(error);
setError("Something went wrong")
}
}
fetchData();
return () => {
if (!response) {
abortController.abort();
}
}
}, [])
const acceptChallengeHandler = ({ challenger, roomID, color, timeLimit }) => {
async function handler() {
const res = await deleteChallenge(roomID, 'accept');
if (res?.success) {
localStorage.setItem('myColor', color === 'b' ? 'w' : 'b');
localStorage.setItem('roomID', roomID);
localStorage.setItem('opponent', challenger);
localStorage.setItem('timeLimit', timeLimit);
navigate(`/game/friend/${roomID}`);
}
}
return handler;
}
const declineChallengeHandler = ({ challenger, roomID, color, timeLimit }) => {
async function handler() {
const res = await deleteChallenge(roomID, 'decline');
}
return handler;
}
const deleteChallenge = async (challengeID, response) => {
try {
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/${userid}/challenges/${challengeID}?response=${response}`
console.log(url)
let res = await fetch(url, {
method: 'DELETE', headers: {
Authorization: `Bearer ${getAuthToken()}`,
}
})
const resData = await res.json();
return resData;
} catch (err) {
console.log(err)
setError(err)
}
}
}, [error])
if (!challenges || challenges.length === 0) {
return (
@@ -93,19 +40,65 @@ const Challenges = () => {
)
}
if (error) {
return (
<>
<Title mt="20px" mb="10px" order={3}>Challenges</Title>
<Text>{error}</Text><Text onClick={() => setError(null)} fw={800} sx={{ cursor: 'pointer' }}>Retry</Text>
</>
)
}
return (
<div>
<Title mt="20px" mb="10px" order={3}>Challenges</Title>
<Stack>
{
challenges.map(({ challenger, roomID, color, timeLimit }) => {
challenges.map(({ id, challenger, roomID, color, timeLimit }, index) => {
console.log(challenger, roomID, color, timeLimit);
return (
<Group position='apart'>
<Group position='apart' key={id}>
<Text>Challenge by {challenger}</Text>
<Group position='center'>
<Button color='lime' onClick={acceptChallengeHandler({ challenger, roomID, color, timeLimit })}>Accept</Button>
<Button color='gray' onClick={declineChallengeHandler({ challenger, roomID, color, timeLimit })}>Decline</Button>
<Button color='lime' onClick={async () => {
try {
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/challenges/${id}`
const response = await fetch(url, { method: 'DELETE', credentials: 'include' });
console.log(id, challenger);
const resData = await response.json();
if (response.ok) {
localStorage.setItem('myColor', color === 'b' ? 'w' : 'b');
localStorage.setItem('roomID', roomID);
localStorage.setItem('opponent', challenger);
localStorage.setItem('timeLimit', timeLimit);
navigate(`/game/friend/${roomID}`);
} else {
console.log(resData)
}
} catch (err) {
setError("Something went wrong");
console.log(err);
}
}}>Accept</Button>
<Button color='gray' onClick={async () => {
try {
let url = `${import.meta.env.VITE_BACKEND_HOST}/api/user/challenges/${id}`
const response = await fetch(url, { method: 'DELETE', credentials: 'include' });
console.log(id, challenger);
if (response.ok) {
challenges.splice(index, 1);
setChallenges(challenges);
} else {
const resData = await response.json();
console.log(resData)
}
} catch (err) {
setError("Something went wrong");
console.log(err);
}
}}>Decline</Button>
</Group>
</Group>
)
+1 -1
View File
@@ -21,7 +21,7 @@ const FriendsList = () => {
No friends
</Text>
:
friends.map((friend, index) => <NavLink key={index} component={Link} to={`/play/friend/${friend.username}`} p='5px' icon={<Avatar size='sm' color='blue' children={friend.username[0].toUpperCase()} />} label={<Text fw={700}>{friend.username}</Text>} />)
friends.map((friend, index) => <NavLink key={index} component={Link} to={`/play/friend/${friend.username}`} p='5px' icon={<Avatar size='sm' color='blue' >{friend.username[0].toUpperCase()}</Avatar>} label={<Text fw={700}>{friend.username}</Text>} />)
}
</Flex>
)
+3 -3
View File
@@ -1,9 +1,9 @@
import React, { useContext } from 'react'
import { ChessGameContext } from '../context/chess-game-context'
import { Button, Flex, Group, ScrollArea, Text, Tooltip, createStyles } from '@mantine/core';
import { Button, Flex, ScrollArea, Tooltip, createStyles } from '@mantine/core';
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'
const useStyles = createStyles(theme => {
const useStyles = createStyles(() => {
return {
movebtn: {
fontSize: '14px',
@@ -26,7 +26,7 @@ const useStyles = createStyles(theme => {
const GameHistory = () => {
let { classes } = useStyles();
const { gameHistory, jumpTo, currentIndex, goBack, goAhead } = useContext(ChessGameContext)
const { gameHistory, jumpTo, goBack, goAhead } = useContext(ChessGameContext)
let gameHistoryJSX = [];
for (let i = 0; i < gameHistory.length;) {
+2 -9
View File
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import { Button, Flex, Modal, Text, Title } from '@mantine/core'
import { Form, redirect, useNavigate } from 'react-router-dom'
import { redirect, useNavigate } from 'react-router-dom'
import { useDisclosure } from '@mantine/hooks'
const Logout = () => {
@@ -27,7 +27,7 @@ const Logout = () => {
localStorage.removeItem('loggedIn');
return navigate('/login');
} else {
return setErrorMsg(resData.userMessage || "Something went wrong")
return setErrorMsg(resData.message || "Something went wrong")
}
} catch (err) {
setIsLoading(false)
@@ -54,11 +54,4 @@ const Logout = () => {
</>
)
}
export const logoutAction = ({ request }) => {
localStorage.removeItem('token');
localStorage.removeItem('user');
return redirect('/login');
}
export default Logout
+2 -2
View File
@@ -1,4 +1,4 @@
import { Group, NavLink, Text, ThemeIcon, UnstyledButton } from '@mantine/core'
import { NavLink, Text, ThemeIcon } from '@mantine/core'
import { GearIcon, HomeIcon, PlayIcon } from '@radix-ui/react-icons'
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
@@ -31,7 +31,7 @@ const NavbarLink = ({ label, icon, to, index, active, setActive }) => {
component={Link}
to={to}
icon={
<ThemeIcon variant="filled" color={active===index?'gray':'lime'}>
<ThemeIcon variant="filled" color={active === index ? 'gray' : 'lime'}>
{icon}
</ThemeIcon>
}
+5 -5
View File
@@ -9,7 +9,7 @@ const useStyles = createStyles((theme) => ({
boxShadow: 'none',
borderColor: 'transparent'
}
}))
}));
const Piece = ({ cell }) => {
const { classes } = useStyles();
@@ -50,7 +50,7 @@ const Piece = ({ cell }) => {
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
cursor: isDragging ? 'grabbing' : 'pointer',
zIndex: isDragging ? 100 : 20,
zIndex: isDragging ? 1000 : 10,
aspectRatio: '1',
touchAction: 'none',
borderRadius: '10px',
@@ -66,15 +66,15 @@ const Piece = ({ cell }) => {
if (logo) {
return (
<div style={{ position: 'relative', zIndex: 100 }}>
<div style={{ position: 'relative' }}>
<div style={{ position: 'absolute', borderRadius: '50%', boxSizing: 'border-box', borderWidth: '8px', width: '100%', height: '100%', borderStyle: 'solid', borderColor }}>
</div>
<Image classNames={{ root: classes['chess-piece'] }} ref={setNodeRef} style={style} sx={{ cursor: 'pointer' }} {...listeners} {...attributes} src={`/src/assets/images/pieces/${logo}.png`} />
<Image classNames={{ root: classes['chess-piece'] }} ref={setNodeRef} style={style} sx={{ cursor: 'pointer' }} {...listeners} {...attributes} src={`/src/assets/images/${logo}.png`} />
</div>
)
} else {
return (
<div style={{ width: '100%' }}></div>
<div style={{ width: '100%', zIndex: 100 }}></div>
)
}
}
+3 -3
View File
@@ -1,9 +1,9 @@
import React, { createContext, useReducer, useRef, useState } from 'react'
import { ChessModified, chessInit } from '../../utils/chess';
import { ChessModified, chessInit } from '../utils/chess';
import { DISPATCH_EVENTS, SOCKET_EVENTS } from '../constants';
import { socket } from '../socket';
const { CAPTURE_PIECE, MOVE_PIECE, SELECT_PIECE, JUMP_TO, SET_GAME_HISTORY, END_GAME } = DISPATCH_EVENTS
const { GAME_END, CHESS_MOVE, CHESS_OPPONENT_MOVE } = SOCKET_EVENTS;
const { GAME_END, CHESS_MOVE } = SOCKET_EVENTS;
export const ChessGameContext = createContext();
// myColor: null, chess: null, chessBoard: null, moveHints: null, selected: null, dispatch: null, handleOpponentMove: null, handleSquareClick: null, getSquareColor: null, isSquareMarked: null, selectPiece: null, handleDrop: null
@@ -171,7 +171,7 @@ const ChessGameContextProvider = ({ children }) => {
}
}
function selectPiece({ square, type, color: pieceColor }) {
function selectPiece({ square, color: pieceColor }) {
if (pieceColor === myColor && myColor === chess.turn()) {
dispatch({ type: SELECT_PIECE, val: square });
}
+3 -6
View File
@@ -1,14 +1,12 @@
import React, { createContext, useEffect, useState } from 'react'
import Cookie from 'js-cookie'
export const UserDataContext = createContext();
export const UserDataContext = createContext();
const UserDataContextProvider = ({ children }) => {
// TODO: use more secure mechanism insted of localstorage
const [isLoggedIn, setIsLoggedIn] = useState(JSON.parse(localStorage.getItem('loggedIn')));
const [user, setUser] = useState(null);
const [errorMessage, setErrorMessage] = useState("");
console.log(user)
async function fetchUserDetails() {
try {
@@ -22,8 +20,7 @@ const UserDataContextProvider = ({ children }) => {
if (response.ok) {
setUser(resData);
} else {
console.log(resData.devMessage);
setErrorMessage(resData.userMessage);
setErrorMessage(resData.message);
}
}
} catch (err) {
@@ -36,7 +33,7 @@ const UserDataContextProvider = ({ children }) => {
}, []);
return (
<UserDataContext.Provider value={{ user, friends: user?.friends , games: user?.games, errorMessage, isLoggedIn, setIsLoggedIn }}>
<UserDataContext.Provider value={{ user, friends: user?.friends, games: user?.games, errorMessage, isLoggedIn, setIsLoggedIn }}>
{children}
</UserDataContext.Provider>
)
+3 -3
View File
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
const useCountDown = (timeLimit,isTimerOn) => {
const useCountDown = (timeLimit, isTimerOn) => {
const [timeLeft, setTimeLeft] = useState(timeLimit * 60 * 1000);
useEffect(() => {
if(!isTimerOn) {
if (!isTimerOn) {
return;
}
if (timeLeft > 0) {
@@ -73,7 +73,7 @@ const LoginForm = () => {
setIsLoggedIn(true);
return navigate('/');
} else {
setErrorMsg(resData.userMessage || "Something went wrong");
setErrorMsg(resData.message || "Something went wrong");
}
} catch (err) {
setIsLoading(false);
@@ -123,8 +123,8 @@ const SignupForm = () => {
} else {
setIsLoading(false);
console.log(resData);
console.log(resData.devMessage);
setErrorMsg(resData.userMessage);
console.log(resData.description);
setErrorMsg(resData.message);
resData?.error?.username && setError('username', { message: resData.error.username });
resData?.error?.email && setError('email', { message: resData.error.email });
}
+16 -12
View File
@@ -1,18 +1,18 @@
import { Avatar, Button, Flex, Group, Image, Loader, MediaQuery, Modal, NavLink, Text, Title } from '@mantine/core'
import React, { useContext, useEffect, useState } from 'react'
import ChessBoard from '../Chess/ChessBoard'
import { useNavigate, useParams } from 'react-router-dom'
import { useNavigate } from 'react-router-dom'
import { socket } from '../../socket'
import { getUserData } from '../../../utils/auth'
import { getUserData } from '../../utils/auth'
import { ChessGameContext } from '../../context/chess-game-context'
import GameHistory from '../../components/GameHistory'
import Timer from './Timer'
import MainLoader from '../../components/MainLoader'
import { useDisclosure } from '@mantine/hooks'
import { SOCKET_EVENTS } from '../../constants'
const { CONNECT, DISCONNECT, CHESS_OPPONENT_MOVE, USER_RESIGNED, CONNECTION, JOIN_ROOM, JOIN_ROOM_ERROR, JOIN_ROOM_SUCCESS, ROOM_FULL, USER_JOINED_ROOM } = SOCKET_EVENTS;
const { CONNECT, DISCONNECT, CHESS_OPPONENT_MOVE, USER_RESIGNED, JOIN_ROOM, JOIN_ROOM_ERROR, JOIN_ROOM_SUCCESS, ROOM_FULL, USER_JOINED_ROOM } = SOCKET_EVENTS;
const ChessGame = () => {
const { setGameHistory, isTimerOn, setIsTimerOn, hasGameEnded, gameEndedReason, endGame } = useContext(ChessGameContext);
const { setGameHistory, hasGameEnded, gameEndedReason, endGame } = useContext(ChessGameContext);
const [gameEndedModalOpen, modalFunctions] = useDisclosure(true);
const user = getUserData();
@@ -67,7 +67,7 @@ const ChessGame = () => {
console.log('Socket disconnected due to', reason);
});
socket.on(CHESS_OPPONENT_MOVE, (data) => {
socket.on(CHESS_OPPONENT_MOVE, () => {
// console.log(data);
// setIsTimerOn(true);
})
@@ -76,9 +76,9 @@ const ChessGame = () => {
setIsWaiting(false);
});
socket.on(USER_RESIGNED, (roomID, username) => {
socket.on(USER_RESIGNED, () => {
endGame('RESIGN');
})
});
return () => {
socket.offAny();
@@ -87,7 +87,7 @@ const ChessGame = () => {
}, []);
if (!hasJoinedRoom) return (
<Loader variant='bars' />
<MainLoader />
)
return (
@@ -104,7 +104,9 @@ const ChessGame = () => {
style={{ width: "500px" }}
p="2px"
label={isWaiting ? "Waiting for opponent..." : opponent}
icon={<Avatar radius="3px" children={opponent[0].toUpperCase()} />}
icon={<Avatar radius="3px" >
{opponent[0].toUpperCase}
</Avatar>}
description={"description"}
/>
{/* <Timer on={!isTimerOn} /> */}
@@ -128,7 +130,9 @@ const ChessGame = () => {
style={{ width: "500px" }}
p="2px"
label={username}
icon={<Avatar radius="3px" children={username[0].toUpperCase()} />}
icon={<Avatar radius="3px" >
{username[0].toUpperCase()}
</Avatar>}
description={"description"}
/>
{/* <Timer on={isTimerOn} /> */}
@@ -140,7 +144,7 @@ const ChessGame = () => {
height: '100%',
textAlign: 'center',
borderRadius: '10px',
backgroundColor:'#272623'
backgroundColor: '#272623'
}} bg='gray' justify='start' py='md' align='center' direction='column' h="600px">
<Title my='20px'>Game Data</Title>
<Flex direction='column' w='100%'>
+1 -1
View File
@@ -4,7 +4,7 @@ import { Box } from '@mantine/core';
import { ChessGameContext } from '../../context/chess-game-context';
const Timer = ({ on }) => {
const { isTimerOn } = useContext(ChessGameContext)
// const { isTimerOn } = useContext(ChessGameContext)
const timeLimit = localStorage.getItem('timeLimit');
const [seconds, minutes] = useCountDown(timeLimit, on);
return (
+12 -13
View File
@@ -1,7 +1,7 @@
import { Avatar, Button, Card, Flex, Image, Select, Text, TextInput, Title } from '@mantine/core'
import { Avatar, Button, Card, Flex, Select, Text, Title } from '@mantine/core'
import React from 'react'
import { Form, redirect, useParams } from 'react-router-dom'
import { getAuthToken, getUserData } from '../../../utils/auth'
import { getUserData } from '../../utils/auth'
const ChallengeFriend = () => {
const params = useParams();
@@ -9,12 +9,12 @@ const ChallengeFriend = () => {
return (
<Card
maw={450} sx={{
width: '100%',
height: '600px',
textAlign: 'center',
backgroundColor:'#262523'
}}
maw={450} sx={{
width: '100%',
height: '600px',
textAlign: 'center',
backgroundColor: '#262523'
}}
>
<Form action={`/play/friend/${friend_username}`} method='POST'>
<Flex align="center" direction="column" justify="center" gap="xs" my="lg">
@@ -42,20 +42,19 @@ export const playFriendAction = async ({ request, params }) => {
let timeLimit = formData.get('timeLimit');
let username = getUserData().username;
let challenged = params.friend_username;
console.log(color, timeLimit, username, challenged);
let roomIDURL = `${import.meta.env.VITE_BACKEND_HOST}/api/room/create`;
let roomIDURL = `${import.meta.env.VITE_BACKEND_HOST}/api/room`;
let reqBody = { challenger: username, challenged, timeLimit, color }
try {
console.log(reqBody);
const response = await fetch(roomIDURL, {
method: 'POST', body: JSON.stringify(reqBody), headers: {
'Authorization': `Bearer ${getAuthToken()}`,
'Content-Type': 'application/json'
}
},
credentials: 'include'
});
console.log(response.status);
const resJSON = await response.json();
const { roomID } = resJSON;
console.log('Room ID:', roomID);
+3 -3
View File
@@ -1,7 +1,7 @@
import { Avatar, Box, Card, Divider, Flex, Image, MediaQuery, NavLink, Text, Title } from '@mantine/core'
import { Avatar, Flex, Image, MediaQuery, NavLink } from '@mantine/core'
import React from 'react'
import { Link, Outlet } from 'react-router-dom'
import { getUserData } from '../../../utils/auth';
import { Outlet } from 'react-router-dom'
import { getUserData } from '../../utils/auth';
const Layout = () => {
const user = getUserData();
+11 -29
View File
@@ -1,48 +1,30 @@
import React, { useState } from 'react'
import { Button, Card, CopyButton, Flex, Group, Image, Modal, NativeSelect, NavLink, Select, Text, TextInput, Title } from '@mantine/core'
import React from 'react'
import { Button, Card, Flex, Image, Modal, NativeSelect, Text, TextInput, Title } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { IconSearch, IconUserCircle } from '@tabler/icons-react'
import { IconSearch } from '@tabler/icons-react'
import FriendsList from '../../components/FriendsList'
import { Form } from 'react-router-dom'
import Challenges from '../../components/Challenges'
const createChallengeLink = (color) => {
let challengeLink = Math.floor(Math.random() * 100_000_000).toString();
if (color === 'RANDOM') {
challengeLink = challengeLink.concat(Math.random() < 0.5 ? 'W' : 'B');
} else {
challengeLink = challengeLink.concat(color);
}
return challengeLink;
}
const PlayFriend = () => {
const [opened, { open, close }] = useDisclosure(false);
const [joinChallengeModalState, modalFunctions] = useDisclosure(false);
return (
<>
<Modal zIndex={10} opened={opened} onClose={close} title={<Text mx="auto" size="xl">Create Challenge Link</Text>} centered>
<Text>Start a game with anyone</Text>
<div>
<NativeSelect onChange={(evt) => setColor(evt.target.value)} my="20px" label={<Text mx="auto" order={3}>I play as</Text>} placeholder='choose your color' data={[
<NativeSelect my="20px" label={<Text mx="auto" order={3}>I play as</Text>} placeholder='choose your color' data={[
{ value: 'W', label: 'White' },
{ value: 'B', label: 'Black' },
{ value: 'RANDOM', label: 'Random' }
]} />
</div>
{/* TODO: update createChallengeLink function */}
{/* <CopyButton>
{
({ copied, copy }) => <Button disabled onClick={copy} color={copied ? 'gray' : 'lime'}> {copied ? 'Copied' : 'Copy Link'} </Button>
}
</CopyButton> */}
</Modal>
<Card
maw={450} sx={{
width: '100%',
height: '600px',
textAlign: 'center',
backgroundColor:'#262523'
backgroundColor: '#262523'
}}
>
<Flex align="center" justify="center" gap="xs" my="lg">
@@ -61,11 +43,11 @@ const PlayFriend = () => {
)
}
const friends = [
{ avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <IconUserCircle />, username: "friend", rating: 100 },
{ avatar: <IconUserCircle />, username: "friend", rating: 100 },
]
// const friends = [
// { avatar: <IconUserCircle />, username: "friend", rating: 100 },
// { avatar: <IconUserCircle />, username: "friend", rating: 100 },
// { avatar: <IconUserCircle />, username: "friend", rating: 100 },
// { avatar: <IconUserCircle />, username: "friend", rating: 100 },
// ]
export default PlayFriend
+3 -7
View File
@@ -1,20 +1,16 @@
import React from 'react'
import { Button, Card, Flex, List, Stack, TextInput, Title } from '@mantine/core'
import { Button, Card, Flex, TextInput, Title } from '@mantine/core'
import { IconSearch } from '@tabler/icons-react'
import { useForm } from '@mantine/form'
import { getAuthToken, getUserData } from '../../../utils/auth'
import FriendsList from '../../components/FriendsList'
const Friends = () => {
let { id: userid } = getUserData();
const form = useForm({ initialValues: { username: '' }, })
const addFriend = async () => {
const response = await fetch(`${import.meta.env.VITE_BACKEND_HOST}/api/user/${userid}/friends/${form.values.username}`, {
const response = await fetch(`${import.meta.env.VITE_BACKEND_HOST}/api/user/friends/${form.values.username}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${getAuthToken()}`
}
credentials: 'include'
});
const resData = await response.json();
if (resData.success === false) {
+4 -4
View File
@@ -1,6 +1,6 @@
import React, { useContext } from 'react'
import { Avatar, Button, Flex, Grid, Group, Loader, Stack, Text, TextInput, Title } from '@mantine/core'
import { getAuthToken, getUserData } from '../../../utils/auth'
import { Avatar, Button, Flex, Grid, Group, Stack, Text, TextInput, Title } from '@mantine/core'
import { getUserData } from '../../utils/auth'
import { UserDataContext } from '../../context/user-data-context'
import { useForm } from '@mantine/form'
import { Form } from 'react-router-dom'
@@ -83,9 +83,9 @@ export const action = async ({ request }) => {
body: JSON.stringify(reqBody),
method: 'PATCH',
headers: {
Authorization: `Bearer ${getAuthToken()}`,
'Content-Type': 'application/json'
}
},
credentials: 'include'
})
const resData = await response.json();
console.log(resData)
+1 -2
View File
@@ -1,10 +1,9 @@
import { Box, Flex, NavLink, Title } from '@mantine/core'
import { ColorWheelIcon, GearIcon, GlobeIcon, LockOpen1Icon, PersonIcon } from '@radix-ui/react-icons'
import React, { useState } from 'react'
import React from 'react'
import { Link, Outlet } from 'react-router-dom';
const Settings = () => {
const [active, setActive] = useState(null);
return (
<Box ml='45px'>
<Flex align="center" my="lg">
+6
View File
@@ -0,0 +1,6 @@
{
"name": "_chess_",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}