Games container sceleton

- use Route for conditional rendering
- useGamesAPI
- Checkers component
This commit is contained in:
djmil 2023-11-08 18:22:05 +01:00
parent b58c71c876
commit aa2a250085
48 changed files with 572 additions and 446 deletions

View File

@ -16,7 +16,7 @@ import djmil.cordacheckers.user.User;
@RestController @RestController
@RequestMapping("api/gamestate") @RequestMapping("api/games")
public class GameStateController { public class GameStateController {
@Autowired @Autowired

View File

@ -1,3 +1,8 @@
.App { [data-darkreader-scheme="dark"] .Header a {
text-align: center; color: darkslategrey;
} }
[data-darkreader-scheme="dark"] .Header .active {
color: white;
box-shadow: 0 1.5px 0 0 currentcolor;
}

View File

@ -1,46 +1,41 @@
import './App.css'; import './App.css';
import React from 'react'; import React from 'react';
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Header from "./container/Header" import Header from './container/Header';
import Leaderboard from "./container/Leaderboard"
import Game from "./components/Game"
import About from "./components/About" import About from "./components/About"
import Games from './container/Games';
import Leaderboard from './container/Leaderboard';
import usePollingReducer from './reducer/polling';
import useUserReducer from './reducer/user'; import useUserReducer from './reducer/user';
import usePollingReducer from './reducer/polling';
import useLeaderboardReducer from './reducer/leaderboard'; import useLeaderboardReducer from './reducer/leaderboard';
import useGamesReducer from './reducer/games';
import useLeaderboardApi from './api/leaderboard';
import useUserApi from './api/user'; import useUserApi from './api/user';
import useLeaderboardApi from './api/leaderboard';
import useGamesApi from './api/games';
function App() { function App() {
const pollingReducer = usePollingReducer();
const userReducer = useUserReducer(); const userReducer = useUserReducer();
const pollingReducer = usePollingReducer();
const leaderboardReducer = useLeaderboardReducer(); const leaderboardReducer = useLeaderboardReducer();
const gamesReducer = useGamesReducer();
const leaderboardApi = useLeaderboardApi(leaderboardReducer);
const userApi = useUserApi(userReducer); const user = useUserApi(userReducer).get();
const leaderboard = useLeaderboardApi(leaderboardReducer).poll(pollingReducer);
const leaderboard = leaderboardApi.get(pollingReducer); /*const gamesApi = */ useGamesApi(gamesReducer).list(pollingReducer);
const user = userApi.get();
return ( return (
<div className="App" > <BrowserRouter>
<BrowserRouter> <Header pollingReducer={pollingReducer} />
<Header pollingReducer={pollingReducer} /> <Routes>
<Routes> <Route path="/" element={<About />} />
{/* https://stackoverflow.com/questions/40541994/multiple-path-names-for-a-same-component-in-react-router */} <Route path="/about" element={<About />} />
<Route path="/game" element={<Game />} /> <Route path="/games/*" element={<Games />} />
<Route path="/game/new" element={<Game />} /> <Route path="/leaderboard" element={<Leaderboard leaderboard={leaderboard} user={user} />} />
<Route path="/game/proposal" element={<Game />} /> </Routes>
<Route path="/game/active" element={<Game />} /> </BrowserRouter>
<Route path="/game/archive" element={<Game />} />
<Route path="/leaderboard" element={<Leaderboard leaderboard={leaderboard} user={user} />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
</div>
) )
} }

31
webapp/src/api/games.js Normal file
View File

@ -0,0 +1,31 @@
import usePolling from "../util/Polling"
const uri = '/api/games';
export default function useGamesApi(gamesReducer) {
const [games, dispatchGames] = gamesReducer;
const useList = (pollingReducer) => {
const [polling, dispatchPolling] = pollingReducer;
const mode = (polling.enabled === true)
? { interval_sec: 30 } // update games list half a minue
: { interval_stop: true } // user has fliped OfflineToggel
const [list, isFetching] = usePolling(uri, mode);
if (polling.games !== isFetching) {
dispatchPolling({ type: 'next', games: isFetching });
}
if (games.list !== list) {
dispatchGames({ type: 'next', list });
}
return games;
}
return {
list: useList
}
}

View File

@ -5,7 +5,7 @@ const uri = '/api/leaderboard';
export default function useLeaderboardApi(leaderboardReducer) { export default function useLeaderboardApi(leaderboardReducer) {
const [leaderboard, dispatchLeaderboaed] = leaderboardReducer; const [leaderboard, dispatchLeaderboaed] = leaderboardReducer;
const useGet = (pollingReducer) => { const usePoll = (pollingReducer) => {
const [polling, dispatchPolling] = pollingReducer; const [polling, dispatchPolling] = pollingReducer;
const mode = (polling.enabled === true) const mode = (polling.enabled === true)
@ -26,6 +26,6 @@ export default function useLeaderboardApi(leaderboardReducer) {
} }
return { return {
get: useGet poll: usePoll
} }
} }

View File

@ -1,4 +1,15 @@
.board { .Stone {
cursor: default; /* disable 'I beam' cursor change */
}
.Player {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.Board {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
@ -6,7 +17,7 @@
/* scale: 15%; */ /* scale: 15%; */
} }
.tile { .Tile {
border: 1px solid #e4e4e4; border: 1px solid #e4e4e4;
float: left; float: left;
font-size: 200%; font-size: 200%;
@ -20,14 +31,14 @@
text-align: center; text-align: center;
} }
.tile.black { .Tile.black {
background: lightgray; background: lightgray;
} }
.tile.white:hover { .Tile.white:hover {
background-color:azure; background-color:azure;
} }
.stone { .Tile .Stone {
font-size: 120%; font-size: 120%;
} }

View File

@ -0,0 +1,130 @@
import './Checkers.css'
import React from 'react'
export const Color = {
white: "WHITE",
black: "BLACK",
opposite: (color) => {
if (color === Color.white)
return Color.black;
if (color === Color.black)
return Color.white;
return color;
}
};
/*
* Stone
*/
export function Stone({ color }) {
switch (color) {
case Color.white:
return WhiteStone();
case Color.black:
return BlackStone();
default:
console.warn("Unknown color: ", color)
}
}
export function WhiteStone() {
return <span className="Stone white"></span>
}
export function BlackStone() {
return <span className="Stone black"></span>
}
/*
* Player
*/
export function Player({ color, name }) {
return (
<div className='Player'>
<Stone color={color} />
{name}
</div>
)
}
/*
* Board
*/
export function Board() {
return <div className='Board'>
<div className='row'>
<BlackTile /> <WhiteTile id={0} stone={WhiteStone()} />
<BlackTile /> <WhiteTile id={1} stone={WhiteStone()} />
<BlackTile /> <WhiteTile id={2} stone={WhiteStone()} />
<BlackTile /> <WhiteTile id={4} stone={WhiteStone()} />
</div>
<div className='row'>
<WhiteTile id={5} stone={WhiteStone()} /> <BlackTile />
<WhiteTile id={6} stone={WhiteStone()} /> <BlackTile />
<WhiteTile id={7} stone={WhiteStone()} /> <BlackTile />
<WhiteTile id={8} stone={WhiteStone()} /> <BlackTile />
</div>
<div className='row'>
<BlackTile /> <WhiteTile id={9} stone={WhiteStone()} />
<BlackTile /> <WhiteTile id={10} stone={WhiteStone()} />
<BlackTile /> <WhiteTile id={11} stone={WhiteStone()} />
<BlackTile /> <WhiteTile id={12} stone={WhiteStone()} />
</div>
<div className='row'>
<WhiteTile id={13} stone={null} /> <BlackTile />
<WhiteTile id={14} stone={null} /> <BlackTile />
<WhiteTile id={15} stone={null} /> <BlackTile />
<WhiteTile id={16} stone={null} /> <BlackTile />
</div>
<div className='row'>
<BlackTile /> <WhiteTile id={17} stone={null} />
<BlackTile /> <WhiteTile id={18} stone={null} />
<BlackTile /> <WhiteTile id={19} stone={null} />
<BlackTile /> <WhiteTile id={20} stone={null} />
</div>
<div className='row'>
<WhiteTile id={21} stone={BlackStone()} /> <BlackTile />
<WhiteTile id={22} stone={BlackStone()} /> <BlackTile />
<WhiteTile id={23} stone={BlackStone()} /> <BlackTile />
<WhiteTile id={24} stone={BlackStone()} /> <BlackTile />
</div>
<div className='row'>
<BlackTile /> <WhiteTile id={25} stone={BlackStone()} />
<BlackTile /> <WhiteTile id={26} stone={BlackStone()} />
<BlackTile /> <WhiteTile id={27} stone={BlackStone()} />
<BlackTile /> <WhiteTile id={28} stone={BlackStone()} />
</div>
<div className='row'>
<WhiteTile id={29} stone={BlackStone()} /> <BlackTile />
<WhiteTile id={30} stone={BlackStone()} /> <BlackTile />
<WhiteTile id={31} stone={BlackStone()} /> <BlackTile />
<WhiteTile id={32} stone={BlackStone()} /> <BlackTile />
</div>
</div>
}
function WhiteTile({ id, stone }) {
return (
<div
className='Tile white'
onClick={() => handleClick(id)}
>
{stone}
</div>
);
}
function BlackTile() {
return <div className='Tile black' />
}
function handleClick(i) {
console.log("click", i)
}

View File

@ -1,26 +0,0 @@
.game {
width: 100%;
float: left;
}
.game .left-side {
float: left;
width: 45%;
/* max-width: 400px; */
/* height: 100px; */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.game .right-side {
float: left;
width: 55%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

View File

@ -1,29 +0,0 @@
import './Game.css';
import React from 'react';
import GameView from './Game/GameView'
import GameSelector from './Game/GameSelector'
import GameAction from './Game/GameAction'
import GameBoard from './Game/GameBoard'
import NewGame from './Game/NewGame'
import GameMessage from './Game/GameMessage'
import Message2Opponent from './Game/Message2Opponent'
export default function Game() {
return (
<div className="game">
<div className='left-side'>
<GameView />
<GameSelector />
<NewGame />
</div>
<div className='right-side'>
<GameAction />
<GameMessage />
<GameBoard />
<Message2Opponent />
</div>
</div>
)
}

View File

@ -1,55 +0,0 @@
.action-panel {
margin-bottom: 10px;
/* background-color: lightgrey; */
width: 100%;
/* padding-top: 8px;
padding-bottom: 8px; */
color: black;
padding-left: -10px;
/* */
margin-left: 10px;
border: 0.5px dotted lightslategray;
}
.game-action {
width:fit-content;
padding: 8px;
padding-left: 15px;
padding-right: 15px;
border-radius: 5px;
border: 0.5px solid darkgrey;
margin: 2px;
}
.game-action.create:hover, /* OR */
.game-action.busy
{
background-color:#00b0ff60;
}
.game-action.create.enabled:active {
background-color:#00b0ffa0;
}
.game-action.cancel:hover,
.game-action.reject:hover {
background-color:#ff000030
}
.game-action.cancel:active,
.game-action.reject:active {
background-color:#ff000080
}
.game-action.accept:hover {
background-color: #00af0030;
}
.game-action.accept:active {
background-color:#00af0080;
}
.game-action.disabled {
color: gray;
}

View File

@ -1,45 +0,0 @@
import './GameAction.css';
import React from 'react';
import { useLocation, matchPath } from "react-router";
import Create from './GameAction/Create';
import Reject from './GameAction/Reject';
import Cancel from './GameAction/Cancel';
import Accept from './GameAction/Accept';
import DrawReq from './GameAction/DrawReq';
import DrawAcq from './GameAction/DrawAcq';
import Surrender from './GameAction/Surrender';
import Backward from './GameAction/Backward';
import Forward from './GameAction/Forward';
// import { AppContext } from '../../context/app'
export default function GameAction() {
// const [ctx, dispatchCtx] = React.useContext(AppContext)
const { pathname } = useLocation();
const isNewGamePath = matchPath("/game/new", pathname);
const isProposalPath = matchPath("/game/proposal/*", pathname);
const isActivelPath = matchPath("/game/active/*", pathname);
const isArchivePath = matchPath("/game/archive/*", pathname);
return (
<div className='action-panel'>
{isNewGamePath && <Create />}
{isProposalPath && <Reject />}
{isProposalPath && <Cancel />}
{isProposalPath && <Accept />}
{isActivelPath && <DrawReq />}
{isActivelPath && <DrawAcq />}
{isActivelPath && <Surrender />}
{isArchivePath && <Backward />}
{isArchivePath && <Forward />}
</div>
)
}

View File

@ -1,6 +0,0 @@
import React from 'react';
export default function Backward() {
return <button className='game-action backward'>Backward</button>
}

View File

@ -1,6 +0,0 @@
import React from 'react';
export default function DrawAcq() {
return <button className='game-action draw-acq'>Draw acquire</button>
}

View File

@ -1,6 +0,0 @@
import React from 'react';
export default function DrawReq() {
return <button className='game-action drawreq'>Draw request</button>
}

View File

@ -1,6 +0,0 @@
import React from 'react';
export default function Forward() {
return <button className='game-action forward'>Forward</button>
}

View File

@ -1,6 +0,0 @@
import React from 'react';
export default function Surrender() {
return <button className='game-action surrender'>Surrender</button>
}

View File

@ -1,3 +0,0 @@
.game-board .board {
padding: 5px;
}

View File

@ -1,20 +0,0 @@
import './GameBoard.css'
import React from 'react'
import Board from './GameBoard/Board'
import { WHITE, BLACK } from './Stone'
import { Player } from './Player'
import { AppContext } from '../../context/app'
export default function GameBoard() {
const [ctx] = React.useContext(AppContext)
return (
<div className='game-board'>
<Player color={WHITE()} name={ctx.newGame.whitePlayer} />
<Board />
<Player color={BLACK()} name={ctx.newGame.blackPlayer} />
</div>
)
}

View File

@ -1,79 +0,0 @@
import './Board.css';
import React from 'react';
import { WhiteStone, BlackStone } from '../Stone'
export default function Board() {
return <div className='board'>
<div className='row'>
<BlackTile/> <WhiteTile id={0} stone={WhiteStone()} />
<BlackTile/> <WhiteTile id={1} stone={WhiteStone()} />
<BlackTile/> <WhiteTile id={2} stone={WhiteStone()} />
<BlackTile/> <WhiteTile id={4} stone={WhiteStone()} />
</div>
<div className='row'>
<WhiteTile id={5} stone={WhiteStone()} /> <BlackTile/>
<WhiteTile id={6} stone={WhiteStone()} /> <BlackTile/>
<WhiteTile id={7} stone={WhiteStone()} /> <BlackTile/>
<WhiteTile id={8} stone={WhiteStone()} /> <BlackTile/>
</div>
<div className='row'>
<BlackTile/> <WhiteTile id={ 9} stone={WhiteStone()} />
<BlackTile/> <WhiteTile id={10} stone={WhiteStone()} />
<BlackTile/> <WhiteTile id={11} stone={WhiteStone()} />
<BlackTile/> <WhiteTile id={12} stone={WhiteStone()} />
</div>
<div className='row'>
<WhiteTile id={13} stone={null} /> <BlackTile/>
<WhiteTile id={14} stone={null} /> <BlackTile/>
<WhiteTile id={15} stone={null} /> <BlackTile/>
<WhiteTile id={16} stone={null} /> <BlackTile/>
</div>
<div className='row'>
<BlackTile/> <WhiteTile id={17} stone={null} />
<BlackTile/> <WhiteTile id={18} stone={null} />
<BlackTile/> <WhiteTile id={19} stone={null} />
<BlackTile/> <WhiteTile id={20} stone={null} />
</div>
<div className='row'>
<WhiteTile id={21} stone={BlackStone()} /> <BlackTile/>
<WhiteTile id={22} stone={BlackStone()} /> <BlackTile/>
<WhiteTile id={23} stone={BlackStone()} /> <BlackTile/>
<WhiteTile id={24} stone={BlackStone()} /> <BlackTile/>
</div>
<div className='row'>
<BlackTile/> <WhiteTile id={25} stone={BlackStone()} />
<BlackTile/> <WhiteTile id={26} stone={BlackStone()} />
<BlackTile/> <WhiteTile id={27} stone={BlackStone()} />
<BlackTile/> <WhiteTile id={28} stone={BlackStone()} />
</div>
<div className='row'>
<WhiteTile id={29} stone={BlackStone()} /> <BlackTile/>
<WhiteTile id={30} stone={BlackStone()} /> <BlackTile/>
<WhiteTile id={31} stone={BlackStone()} /> <BlackTile/>
<WhiteTile id={32} stone={BlackStone()} /> <BlackTile/>
</div>
</div>
}
function WhiteTile({ id, stone }) {
return (
<div
className='tile white'
onClick={() => handleClick(id)}
>
{stone}
</div>
);
}
function BlackTile() {
return <div className='tile black'/>
}
function handleClick(i) {
console.log("click", i)
}

View File

@ -1,7 +1,7 @@
import './Selectable.css' import './Selectable.css'
import React from 'react'; import React from 'react';
import { oppositeColor } from '../Stone'; import { oppositeColor } from '../Stone';
import { Player } from '../Player'; import { Player } from '../../Player';
export default function Selectable({ game, onClick }) { export default function Selectable({ game, onClick }) {

View File

@ -3,7 +3,7 @@ import React from 'react';
import { AppData } from '../../context/data' import { AppData } from '../../context/data'
import { AppContext } from '../../context/app' import { AppContext } from '../../context/app'
import { useLocation, matchPath } from "react-router"; import { useLocation, matchPath } from "react-router";
import { SelectPlayer } from './Player'; import { SelectPlayer } from '../Player';
import { WhiteStone, BlackStone } from './Stone'; import { WhiteStone, BlackStone } from './Stone';

View File

@ -1,15 +0,0 @@
.player {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.player select {
border-radius: 5px;
border: 0.5px solid darkgrey;
}
.player select:hover {
background: lightgray;
}

View File

@ -1,29 +0,0 @@
import './Player.css'
import React from 'react'
import { Stone } from './Stone'
export function Player({ color, name }) {
return (
<div className='player'>
<Stone color={color} />
{name}
</div>
)
}
export function SelectPlayer({ name, setName, nameOptions }) {
const handleSelectChange = (event) => {
setName(event.target.value)
}
return (
<div className='select player'>
<form>
<select value={name} onChange={handleSelectChange}>
{nameOptions}
</select>
</form>
</div>
)
}

View File

@ -1,3 +0,0 @@
.stone {
cursor: default; /* disable 'I beam' cursor change */
}

View File

@ -1,41 +0,0 @@
import './Stone.css'
import React from 'react'
export function Stone({ color }) {
switch (color) {
case WHITE():
return WhiteStone()
case BLACK():
return BlackStone()
default:
console.warn("Unknown color: ", color)
}
}
export function WhiteStone() {
return <span className="stone white"></span>
}
export function BlackStone() {
return <span className="stone black"></span>
}
export function oppositeColor(color) {
if (color === WHITE())
return BLACK()
if (color === BLACK())
return WHITE()
return color
}
export function WHITE() {
return "WHITE"
}
export function BLACK() {
return "BLACK"
}

View File

@ -0,0 +1,125 @@
.Games {
width: 100%;
float: left;
}
.Games .left-side {
float: left;
width: 45%;
/* max-width: 400px; */
/* height: 100px; */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.Games .right-side {
float: left;
width: 55%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.ViewSelector {
text-align: center;
margin-bottom: 10px;
background-color: lightgrey;
width: 100%;
padding-top: 8px;
padding-bottom: 8px;
color: black;
}
.ViewSelector a {
color: black;
text-decoration: none;
transition: .25s ease;
margin-left: 5px;
margin-right: 5px;
}
.ViewSelector .active {
color: white;
border-radius: 2px;
background-color: cadetblue;
opacity: 80%;
padding-top: 8px;
padding-bottom: 8px;
}
.ViewSelector a:hover:not(.active) {
color: cadetblue;
box-shadow: 0 1.5px 0 0 currentColor;
}
.ViewProvider {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.ActionPanel {
text-align: center;
margin-bottom: 10px;
height: 34.5px;
/* background-color: lightgrey; */
width: 100%;
/* padding-top: 8px;
padding-bottom: 8px; */
color: black;
padding-left: -10px;
/* */
margin-left: 10px;
border: 0.5px dotted lightslategray;
}
.ActionPanel button {
width:fit-content;
padding: 6px;
padding-left: 15px;
padding-right: 15px;
border-radius: 5px;
border: 0.5px solid darkgrey;
margin: 2px;
}
.ActionPanel .Create:hover, /* OR */
.game-action.busy
{
background-color:#00b0ff60;
}
.ActionPanel .Create.enabled:active {
background-color:#00b0ffa0;
}
.ActionPanel .Cancel:hover,
.ActionPanel .Reject:hover {
background-color:#ff000030
}
.ActionPanel .Cancel:active,
.ActionPanel .Reject:active {
background-color:#ff000080
}
.ActionPanel .Accept:hover {
background-color: #00af0030;
}
.ActionPanel .Accept:active {
background-color:#00af0080;
}

View File

@ -0,0 +1,77 @@
import './Games.css';
import React from "react"
import { NavLink, Routes, Route } from "react-router-dom";
import NewGame from './games/view/NewGame';
import GameProposals from './games/view/GameProposals';
import ActiveGames from './games/view/ActiveGames';
import GamesArchive from './games/view/GamesArchive';
import Create from './games/action/Create';
import Reject from './games/action/Reject';
import Cancel from './games/action/Cancel';
import Accept from './games/action/Accept';
import DrawReq from './games/action/DrawReq';
import DrawAcq from './games/action/DrawAcq';
import Surrender from './games/action/Surrender';
import Backward from './games/action/Backward';
import Forward from './games/action/Forward';
import GameBoard from './games/GameBoard';
export default function Games({ gamesReducer, user }) {
return (
<div className="Games">
<div className='left-side'>
<ViewSelector />
<ViewProvider />
</div>
<div className='right-side'>
<ActionPanel />
<GameBoard />
{/*
<GameMessage />
<GameBoard />
<Message2Opponent /> */}
</div>
</div>
)
};
function ViewSelector() {
return (
<nav className='ViewSelector'>
<NavLink to="new">New</NavLink>
<NavLink to="proposal">Proposal</NavLink>
<NavLink to="active">Active</NavLink>
<NavLink to="archive">Archive</NavLink>
</nav>
)
}
function ViewProvider() {
return (
<div className='ViewProvider'>
<Routes>
<Route path="new" element={<NewGame />} />
<Route path="proposal" element={<GameProposals />} />
<Route path="active" element={<ActiveGames />} />
<Route path="archive" element={<GamesArchive />} />
</Routes>
</div>
)
}
function ActionPanel() {
return (
<div className='ActionPanel'>
<Routes>
<Route path="new" element={<Create />} />
<Route path="proposal" element={[<Accept key={1} />, <Reject key={2} />, <Cancel key={3} />]} />
<Route path="active" element={[<DrawReq key={1} />, <DrawAcq key={2} />, <Surrender key={3} />]} />
<Route path="archive" element={[<Backward key={1} />, <Forward key={2} />]} />
</Routes>
</div>
)
}

View File

@ -17,7 +17,7 @@
} }
.Header a { .Header a {
color: lightgray; color: black;
text-decoration: none; text-decoration: none;
transition: .25s ease; transition: .25s ease;
width: fit-content; width: fit-content;
@ -38,14 +38,5 @@
.Header a:hover:not(.active) { .Header a:hover:not(.active) {
color: cadetblue; color: cadetblue;
box-shadow: 0 1.5px 0 0 currentcolor;
}
[data-darkreader-scheme="dark"] .Header a {
color: darkslategrey;
}
[data-darkreader-scheme="dark"] .Header .active {
color: white;
box-shadow: 0 1.5px 0 0 currentcolor; box-shadow: 0 1.5px 0 0 currentcolor;
} }

View File

@ -23,8 +23,8 @@ export default function Header({ pollingReducer }) {
About About
</NavLink> </NavLink>
<NavLink to="/game"> <NavLink to="/games">
<Wobler text="Game" dance={polling.games} /> <Wobler text="Games" dance={polling.games} />
</NavLink> </NavLink>
<NavLink to="/leaderboard"> <NavLink to="/leaderboard">

View File

@ -22,19 +22,21 @@ export default function Leaderboard({ leaderboard, user }) {
</tr> </tr>
}); });
return <div className="Leaderboard"> return (
<table> <div className="Leaderboard">
<thead> <table>
<tr> <thead>
<th></th> <tr>
<th>Played</th> <th></th>
<th>Won</th> <th>Played</th>
<th>Draw</th> <th>Won</th>
</tr> <th>Draw</th>
</thead> </tr>
<tbody> </thead>
{tableRows} <tbody>
</tbody> {tableRows}
</table> </tbody>
</div> </table>
</div>
)
}; };

View File

@ -0,0 +1,3 @@
.GameBoard .Board {
padding: 5px;
}

View File

@ -0,0 +1,20 @@
import './GameBoard.css'
import React from 'react'
import { Color, Player, Board } from '../../components/Checkers';
//import { AppContext } from '../../context/app'
export default function GameBoard() {
//const [ctx] = React.useContext(AppContext)
return (
<div className='GameBoard'>
<Player color={Color.white} name={/*ctx.newGame.whitePlayer*/"White player name"} />
<Board />
<Player color={Color.black} name={/*ctx.newGame.blackPlayer*/"Black player name"} />
</div>
)
}

View File

@ -2,5 +2,5 @@ import React from 'react';
export default function Accept() { export default function Accept() {
return <button className='game-action accept'>Accept</button> return <button className='Accept'>Accept</button>
} }

View File

@ -0,0 +1,6 @@
import React from 'react';
export default function Backward() {
return <button className='Backward' disabled>Backward</button>
}

View File

@ -2,5 +2,5 @@ import React from 'react';
export default function Cancel() { export default function Cancel() {
return <button className='game-action cancel'>Cancel</button> return <button className='Cancel'>Cancel</button>
} }

View File

@ -0,0 +1,6 @@
import React from 'react';
export default function Create() {
return <button className='Create'>Create</button>
}

View File

@ -0,0 +1,6 @@
import React from 'react';
export default function DrawAcq() {
return <button className='DrawAcq'>Draw accept</button>
}

View File

@ -0,0 +1,6 @@
import React from 'react';
export default function DrawReq() {
return <button className='DrawReq'>Draw request</button>
}

View File

@ -0,0 +1,6 @@
import React from 'react';
export default function Forward() {
return <button className='Forward' disabled>Forward</button>
}

View File

@ -2,5 +2,5 @@ import React from 'react';
export default function Reject() { export default function Reject() {
return <button className='game-action reject'>Reject</button> return <button className='Reject'>Reject</button>
} }

View File

@ -0,0 +1,6 @@
import React from 'react';
export default function Surrender() {
return <button className='Surrender'>Surrender</button>
}

View File

@ -0,0 +1,7 @@
import React from 'react';
export default function ActiveGames() {
return (
<div>View: ActiveGame</div>
)
}

View File

@ -0,0 +1,7 @@
import React from 'react';
export default function GameProposals() {
return (
<div>View: GameProposals</div>
)
}

View File

@ -0,0 +1,7 @@
import React from 'react';
export default function GamesArchive() {
return (
<div>View: GameArchive</div>
)
}

View File

@ -0,0 +1,8 @@
.SelectPlayer {
border-radius: 5px;
border: 0.5px solid darkgrey;
}
.SelectPlayer select:hover {
background: lightgray;
}

View File

@ -0,0 +1,25 @@
import React from 'react';
export default function NewGame() {
return (
<div>View: NewGame</div>
)
}
// Move to components as DropSelector
function SelectPlayer({ name, setName, nameOptions }) {
const handleSelectChange = (event) => {
setName(event.target.value)
}
return (
<div className='SelectPlayer'>
<form>
<select value={name} onChange={handleSelectChange}>
{nameOptions}
</select>
</form>
</div>
)
}

View File

@ -0,0 +1,21 @@
import { useReducer } from 'react';
import { nextState } from '../util/StateHelper';
export const gamesInitialState = {
list: null,
};
export function gamesReducer(state, action) {
switch (action.type) {
case 'next':
return nextState(state, action);
default:
throw Error('GameReducer: unknown action.type', action.type);
}
}
export default function useGamesReducer() {
return useReducer(gamesReducer, gamesInitialState);
}

View File

@ -17,7 +17,7 @@ export function userReducer(state, action) {
return nextState(state, action); return nextState(state, action);
default: default:
throw Error('Unknown action.type', action.type); throw Error('UserReducer: unknown action.type', action.type);
} }
} }