diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js index 6f3cb01..1cb0d3b 100644 --- a/webapp/src/api/games.js +++ b/webapp/src/api/games.js @@ -73,11 +73,12 @@ export default function useGamesApi() { const pushGameMove = ({ uuid, move, message }) => { doPushing(`/api/game/${uuid}/move`, 'PUT', { move, message }, { - onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && {moveFrom: move[0], moveTo: move[1]} }), + onPushing: (isPushing) => dispatchGuide({ type: 'UUIDpushing', uuid, what: isPushing && { moveFrom: move[0], moveTo: move[1] } }), + onBadReq: (message) => dispatchGuide({ type: 'UUIDerror', uuid, error: { message, moveFrom: move[0], moveTo: move[1] } }), onSuccess: (game) => { dispatchState({ type: 'update', game }); - dispatchGuide({ type: 'UUIDmessage', uuid, message: ''}); - } + dispatchGuide({ type: 'UUIDmessage', uuid, message: '' }); + }, }) } diff --git a/webapp/src/container/Games.css b/webapp/src/container/Games.css index 5b5dca7..27ce429 100644 --- a/webapp/src/container/Games.css +++ b/webapp/src/container/Games.css @@ -97,10 +97,25 @@ .GameBoard { padding-left: 30px; + width: 275px; } .Message2Opponent { margin: 10px; margin-left: 30px; - width: 270px; + width: 275px; +} + +.BadMove { + position: fixed; + width: 275px; + top: 250px; + cursor: default; /* disable 'I beam' cursor change */ + text-align: center; + border-radius: 3px; + background-color: rgba(255, 80, 80, 0.3); +} + +.BadMove:hover { + background-color: rgba(255, 80, 80, 0.7); } \ No newline at end of file diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index 7a3e880..44d5f4f 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -34,7 +34,6 @@ export default function Games({ games, players }) { - {/* */} @@ -156,10 +155,11 @@ function GameBoardRoutes({ dispatchGuide, gamesApi, username }) { const games = useContext(GamesStateContext); const guide = useContext(GamesGuideContext); - const fromUUID = (uuid) => (!uuid) ? [{}, null] : + const fromUUID = (uuid) => (!uuid) ? [{}, null, null] : [ games.find((game) => game.uuid === uuid) || {}, // game - guide.UUIDpushing[uuid] // pushing + guide.UUIDpushing[uuid], // pushing + guide.UUIDerror[uuid] // error (aka bad move) ]; const onStoneClick = (uuid, cellId) => { @@ -191,6 +191,7 @@ function GameBoardRoutes({ dispatchGuide, gamesApi, username }) { username={username} getGame={() => fromUUID(guide.selectedUUID.active)} onStoneMove={(uuid, move) => gamesApi.pushGameMove({ uuid, move, message: guide.UUIDmessage[uuid] })} + dispatchGuide={dispatchGuide} /> } /> diff --git a/webapp/src/container/games/BadMove.jsx b/webapp/src/container/games/BadMove.jsx new file mode 100644 index 0000000..16dac2d --- /dev/null +++ b/webapp/src/container/games/BadMove.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export default function BadMove({ dispatchGuide, uuid, message }) { + if (!dispatchGuide || !uuid || !message) + return; + + return ( +
dispatchGuide({ type: 'UUIDerror', uuid, error: {} })} + > + {message} +
+ ) +} \ No newline at end of file diff --git a/webapp/src/container/games/GameBoard.jsx b/webapp/src/container/games/GameBoard.jsx index 437048e..afd8303 100644 --- a/webapp/src/container/games/GameBoard.jsx +++ b/webapp/src/container/games/GameBoard.jsx @@ -1,10 +1,10 @@ import React from 'react'; - import { Color, Player, Board } from '../../components/Checkers'; import './GameBoard.css'; +import BadMove from './BadMove'; -export default function GameBoard({ username, getGame, onStoneClick, onStoneMove }) { - const [game, isPushing] = getGame(); +export default function GameBoard({ dispatchGuide, username, getGame, onStoneClick, onStoneMove }) { + const [game, isPushing, error] = getGame(); const myName = game.opponentName ? username : ''; const opponentColor = Color.opposite(game.myColor); @@ -14,11 +14,17 @@ export default function GameBoard({ username, getGame, onStoneClick, onStoneMove (cellId) => onStoneClick(game.uuid, cellId); const optionalOnStoneMove = (typeof onStoneMove !== 'function' || isPushing) ? null : - (move) => { + (move) => { if (move[0] !== move[1] && game.board[move[1]] === undefined) onStoneMove(game.uuid, move) }; + const currMove = (() => { + if (isPushing) return [isPushing.moveFrom, isPushing.moveTo]; + if (error?.moveFrom && error?.moveTo) return [error.moveFrom, error.moveTo]; + return []; + })(); // <<-- execute + return (
@@ -37,6 +43,7 @@ export default function GameBoard({ username, getGame, onStoneClick, onStoneMove color={game.myColor || Color.white} name={myName} /> +
) } \ No newline at end of file diff --git a/webapp/src/hook/api.js b/webapp/src/hook/api.js index 2e5b71b..2fc88a3 100644 --- a/webapp/src/hook/api.js +++ b/webapp/src/hook/api.js @@ -21,7 +21,7 @@ export function usePolling(uri, { onSuccess, onPolling }, mode = null) { setPolling(true); if (onPolling) onPolling(true); - + initialPollRef.current = false; fetch(uri) @@ -62,7 +62,7 @@ export function usePolling(uri, { onSuccess, onPolling }, mode = null) { }, [initialPoll, mode, intervalTimerId, isPolling, pollData, stopPollInterval]); } -export async function doPushing(uri, method, data, { onSuccess, onPushing }) { +export async function doPushing(uri, method, data, { onSuccess, onBadReq, onPushing }) { if (onPushing) onPushing(true); @@ -75,14 +75,22 @@ export async function doPushing(uri, method, data, { onSuccess, onPushing }) { body: JSON.stringify(data), // body data type must match "Content-Type" header }); + if (response.status === 400 && onBadReq) { + const content = (response.headers.get('Content-Type') === "application/json") + ? await response.json() + : {}; + + return onBadReq(content.message); + } + if (!response.ok) { return console.warn(`Unexpected response status: ${response.status}`, response); } if (onSuccess) { - var content = (response.headers.get('Content-Type') === "application/json") + const content = (response.headers.get('Content-Type') === "application/json") ? await response.json() - : null; + : {}; onSuccess(content); } diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js index 14aa963..a7092d0 100644 --- a/webapp/src/reducer/games.js +++ b/webapp/src/reducer/games.js @@ -76,6 +76,9 @@ export const gamesGuideTemplate = { UUIDpushing: { // UUIDpushing[uuid] }, + UUIDerror: { // UUIDerror[uuid] + }, + isPolling: false, }; @@ -116,6 +119,12 @@ function gamesGuideReducer(state, action) { return next; } + case 'UUIDerror': { + const next = { ...state }; + next.UUIDerror[action.uuid] = action.error; + return next; + } + default: throw Error('GamesGuide: unknown action.type: ' + action.type); }