refactor join_game_request to require unique users
This commit is contained in:
parent
a3180b0bde
commit
d7c024e286
12 changed files with 191 additions and 16 deletions
|
@ -3,12 +3,53 @@ import { Link } from 'react-router-dom';
|
||||||
import './Game.scss';
|
import './Game.scss';
|
||||||
|
|
||||||
const GameButton = (props) => {
|
const GameButton = (props) => {
|
||||||
const gameData = props.game;
|
const { game, dispatch } = props;
|
||||||
|
|
||||||
|
const requestJoinGame = () => {
|
||||||
|
console.log(`request to Join Game ${game.id}!`)
|
||||||
|
const requestAction = {
|
||||||
|
type: 'GAMES',
|
||||||
|
message: 'JOIN_REQUEST',
|
||||||
|
body: {id: game.id}
|
||||||
|
}
|
||||||
|
dispatch(requestAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderOpenGame = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a onClick={() => requestJoinGame()} >Request to Join Game</a>
|
||||||
|
|
||||||
|
<div className="Game__playerData">
|
||||||
|
<span className="Game__playerData__name">{game.playerBlack}</span>
|
||||||
|
<span className="Game__playerData__rank">{game.playerBlackRank}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderInProgressGame = () => {
|
||||||
|
const gameLinkText = game.winType ? 'Study Game' : 'Watch Game'
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Link to={`/games/${game.id}`}>{gameLinkText}</Link>
|
||||||
|
|
||||||
|
<div className="Game__playerData">
|
||||||
|
<span className="Game__playerData__name">{game.playerBlack}</span>
|
||||||
|
<span className="Game__playerData__rank">{game.playerBlackRank}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="Game__playerData">
|
||||||
|
<span className="Game__playerData__name">{game.playerWhite}</span>
|
||||||
|
<span className="Game__playerData__rank">{game.playerWhiteRank}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="GameButton" data-testid="GameButton">
|
<div className="GameButton" data-testid="GameButton">
|
||||||
<Link to={`/games/${gameData.id}`}>View Game</Link>
|
{game.open ? renderOpenGame() : renderInProgressGame()}
|
||||||
<p>{gameData.playerBlack} - {gameData.playerBlackRank}</p>
|
|
||||||
<p>{gameData.playerWhite} - {gameData.playerWhiteRank}</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
div.Game__playerData {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './ActionError.scss';
|
||||||
|
|
||||||
|
const ActionError = (props) => {
|
||||||
|
const errorMessage = props.error;
|
||||||
|
return (
|
||||||
|
<span data-testid="ActionError" className="ActionError">
|
||||||
|
{errorMessage}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionError;
|
|
@ -0,0 +1,5 @@
|
||||||
|
@import '../../../../public/stylesheets/partials/variables';
|
||||||
|
|
||||||
|
span.FormError {
|
||||||
|
color: map-get($colors, "error");;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import ActionError from './ActionError';
|
||||||
|
|
||||||
|
test('renders ActionError without crashing', () => {
|
||||||
|
const { getByTestId } = render(<ActionError />);
|
||||||
|
const ActionErrorSpan = getByTestId('ActionError');
|
||||||
|
expect(ActionErrorSpan).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders ActionError with error message', () => {
|
||||||
|
const errorMessage = "User already exists!";
|
||||||
|
const { getByTestId } = render(<ActionError error={errorMessage}/>);
|
||||||
|
const ActionErrorSpan = getByTestId('ActionError');
|
||||||
|
expect(ActionErrorSpan).toHaveTextContent(errorMessage);
|
||||||
|
|
||||||
|
})
|
|
@ -6,6 +6,7 @@ import config from '../../config';
|
||||||
import roomsServices from '../../services/api/roomsServices';
|
import roomsServices from '../../services/api/roomsServices';
|
||||||
import GameButton from '../../components/Button/Game/Game';
|
import GameButton from '../../components/Button/Game/Game';
|
||||||
import Message from '../../components/Display/Message/Message';
|
import Message from '../../components/Display/Message/Message';
|
||||||
|
import ActionError from '../../components/Error/ActionError/ActionError';
|
||||||
|
|
||||||
import Development from '../../components/Display/Development/Development';
|
import Development from '../../components/Display/Development/Development';
|
||||||
|
|
||||||
|
@ -13,8 +14,7 @@ const Room = (props) => {
|
||||||
const state = props.state;
|
const state = props.state;
|
||||||
const dispatch = props.dispatch;
|
const dispatch = props.dispatch;
|
||||||
const roomId = parseInt(useParams().id) || 0;
|
const roomId = parseInt(useParams().id) || 0;
|
||||||
const [ socketData, setSocketData ] = useState();
|
const [ socketData, setSocketData ] = useState(false);
|
||||||
const [ messages, setMessages ] = useState();
|
|
||||||
|
|
||||||
const fetchRoomAPI = async () => {
|
const fetchRoomAPI = async () => {
|
||||||
const response = await roomsServices.getRoomService(roomId);
|
const response = await roomsServices.getRoomService(roomId);
|
||||||
|
@ -38,15 +38,36 @@ const Room = (props) => {
|
||||||
const roomSocketConnect = () => {
|
const roomSocketConnect = () => {
|
||||||
roomSocket.emit('connect');
|
roomSocket.emit('connect');
|
||||||
// ! dispatch data
|
// ! dispatch data
|
||||||
roomSocket.on('connected', data => setSocketData('room socket connected'));
|
roomSocket.on('connect', socket => {
|
||||||
roomSocket.on('connect_error', err => console.log(err));
|
setSocketData(true)
|
||||||
roomSocket.on('error', err => console.log(err));
|
});
|
||||||
|
roomSocket.on('join_game_request', data => {
|
||||||
|
console.log(data)
|
||||||
|
})
|
||||||
|
roomSocket.on('connect_error', err => {
|
||||||
|
setSocketData(false)
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
roomSocket.on('error', err => {
|
||||||
|
setSocketData(false)
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
roomSocketConnect();
|
roomSocketConnect();
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = {
|
||||||
|
user: state.user,
|
||||||
|
game: state.joinGame
|
||||||
|
};
|
||||||
|
console.log('emitting request')
|
||||||
|
console.log(data)
|
||||||
|
roomSocket.emit('join_game_request', data)
|
||||||
|
}, [state.joinGame])
|
||||||
|
|
||||||
// ! [end]
|
// ! [end]
|
||||||
|
|
||||||
const renderGames = () => {
|
const renderGames = () => {
|
||||||
|
@ -56,6 +77,7 @@ const Room = (props) => {
|
||||||
<GameButton
|
<GameButton
|
||||||
key={`game-${gameData.id}`}
|
key={`game-${gameData.id}`}
|
||||||
game={gameData}
|
game={gameData}
|
||||||
|
dispatch={dispatch}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -78,7 +100,11 @@ const Room = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Room" data-testid="Room">
|
<div className="Room" data-testid="Room">
|
||||||
<h2>{state.currentRoom ? state.currentRoom.name : 'Loading'}</h2>
|
<div className="Room__heading">
|
||||||
|
<h2>{state.currentRoom ? state.currentRoom.name : 'Loading'}</h2>
|
||||||
|
<span className="Room__connection">{socketData ? '✓' : ' ⃠'}</span>
|
||||||
|
{state.errors.joinGame ? <ActionError error={state.errors.joinGame}/> : <></>}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="Room__game-container">
|
<div className="Room__game-container">
|
||||||
{renderGames()}
|
{renderGames()}
|
||||||
|
|
|
@ -9,6 +9,9 @@ export const errorReducer = (state: state, action: action):state => {
|
||||||
case 'JOIN_ROOM_ERROR':
|
case 'JOIN_ROOM_ERROR':
|
||||||
return joinRoomErrorReducer(state, action);
|
return joinRoomErrorReducer(state, action);
|
||||||
|
|
||||||
|
case 'JOIN_GAME_ERROR':
|
||||||
|
return joinGameErrorReducer(state, action);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -23,3 +26,8 @@ function joinRoomErrorReducer(state: state, action: action): state {
|
||||||
const joinRoom = action.body.joinRoomError;
|
const joinRoom = action.body.joinRoomError;
|
||||||
return { ...state, errors: {joinRoom} }
|
return { ...state, errors: {joinRoom} }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function joinGameErrorReducer(state: state, action: action): state {
|
||||||
|
const joinGame = action.body.joinGameError;
|
||||||
|
return { ...state, errors: {joinGame} }
|
||||||
|
}
|
|
@ -6,11 +6,51 @@ export const gamesReducer = (state: state, action: action):state => {
|
||||||
switch(action.message) {
|
switch(action.message) {
|
||||||
|
|
||||||
case 'SET_GAMES':
|
case 'SET_GAMES':
|
||||||
const games = action.body;
|
const games = formatGames(action);;
|
||||||
return {...state, games};
|
return {...state, games};
|
||||||
|
|
||||||
|
case 'JOIN_REQUEST':
|
||||||
|
if (!Object.entries(state.user).length) {
|
||||||
|
const errAction = {
|
||||||
|
type: 'ERR',
|
||||||
|
message: 'JOIN_GAME_ERROR',
|
||||||
|
body: {joinGameError: 'user not logged in'}
|
||||||
|
}
|
||||||
|
return stateReducer(state, errAction)
|
||||||
|
}
|
||||||
|
const id = action.body;
|
||||||
|
return {...state, joinGame: id};
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseRank(rank: string): string {
|
||||||
|
switch(rank[0]) {
|
||||||
|
case 'D':
|
||||||
|
return `${rank.slice(1)}${rank[0].toLowerCase()}`
|
||||||
|
case 'K':
|
||||||
|
return `${rank.slice(1)}${rank[0].toLowerCase()}`
|
||||||
|
case 'U':
|
||||||
|
return '?'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatGames(action: action): Array<{}> {
|
||||||
|
const games = [...action.body].map(game => {
|
||||||
|
|
||||||
|
if (game.playerBlackRank) {
|
||||||
|
game.playerBlackRank = parseRank(game.playerBlackRank)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.playerWhiteRank) {
|
||||||
|
game.playerWhiteRank = parseRank(game.playerWhiteRank)
|
||||||
|
}
|
||||||
|
|
||||||
|
return game;
|
||||||
|
})
|
||||||
|
|
||||||
|
return games;
|
||||||
|
}
|
|
@ -5,6 +5,10 @@ import type { state } from '../stateReducer';
|
||||||
export const initState = (): state => {
|
export const initState = (): state => {
|
||||||
return {
|
return {
|
||||||
user: {},
|
user: {},
|
||||||
errors: {}
|
errors: {},
|
||||||
|
currentRoom: {},
|
||||||
|
messages: {},
|
||||||
|
games: {},
|
||||||
|
joinGame: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -32,6 +32,7 @@ const getRoomService = async (roomIndex) => {
|
||||||
delete Object.assign(game, {playerBlackRank: game.player_black_rank }).player_black_rank;
|
delete Object.assign(game, {playerBlackRank: game.player_black_rank }).player_black_rank;
|
||||||
delete Object.assign(game, {playerWhite: game.player_white }).player_white;
|
delete Object.assign(game, {playerWhite: game.player_white }).player_white;
|
||||||
delete Object.assign(game, {playerWhiteRank: game.player_white_rank }).player_white_rank;
|
delete Object.assign(game, {playerWhiteRank: game.player_white_rank }).player_white_rank;
|
||||||
|
delete Object.assign(game, {winType: game.win_type }).win_type;
|
||||||
return game;
|
return game;
|
||||||
})
|
})
|
||||||
return obj;
|
return obj;
|
||||||
|
|
|
@ -18,6 +18,8 @@ const getAll = async (req, res, next) => {
|
||||||
const show = async (req, res, next) => {
|
const show = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const roomId = req.params.id;
|
const roomId = req.params.id;
|
||||||
|
if (!roomId) throw('missing room parameter')
|
||||||
|
|
||||||
// TODO eventually add check for user's private rooms
|
// TODO eventually add check for user's private rooms
|
||||||
enableRoomSocket(roomId);
|
enableRoomSocket(roomId);
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,18 @@ io.on('connection', ()=> {
|
||||||
enableRoomSocket = (roomId) => {
|
enableRoomSocket = (roomId) => {
|
||||||
const roomSocket = io.of(roomId);
|
const roomSocket = io.of(roomId);
|
||||||
roomSocket.on('connection', (socket) => {
|
roomSocket.on('connection', (socket) => {
|
||||||
// socket.emit('connected');
|
|
||||||
console.log(`Socket connected at room ${roomId}`);
|
//! Join Game Request queries db for game, ensures unique player joining
|
||||||
socket.on('join_game_request', async data => {
|
socket.on('join_game_request', async data => {
|
||||||
const gameRequest = await logJoinGameRequest(data);
|
const gameRequest = await logJoinGameRequest(data);
|
||||||
|
|
||||||
|
if (gameRequest.err) {
|
||||||
|
roomSocket.emit('join_game_request_error', gameRequest.err);
|
||||||
|
}
|
||||||
|
|
||||||
roomSocket.emit('join_game_request', gameRequest);
|
roomSocket.emit('join_game_request', gameRequest);
|
||||||
})
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
return roomSocket;
|
return roomSocket;
|
||||||
}
|
}
|
||||||
|
@ -29,5 +35,12 @@ module.exports = {
|
||||||
async function logJoinGameRequest (data) {
|
async function logJoinGameRequest (data) {
|
||||||
const {user, game} = data;
|
const {user, game} = data;
|
||||||
const requestedGame = await gameQueries.findGameById(game.id);
|
const requestedGame = await gameQueries.findGameById(game.id);
|
||||||
return { user, requestedGame }
|
|
||||||
|
if (requestedGame.user_black === user.id) {
|
||||||
|
return { err: 'players must be unique' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestingUser = {...user};
|
||||||
|
delete requestingUser.email;
|
||||||
|
return { requestingUser, requestedGame }
|
||||||
}
|
}
|
Loading…
Reference in a new issue