diff --git a/package-lock.json b/package-lock.json
index be99196..9e7aa38 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,8 @@
"@types/node": "^16.18.14",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
+ "bootstrap-icons": "^1.11.3",
+ "framer-motion": "^11.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-query": "^3.39.3",
@@ -1808,16 +1810,21 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
},
"node_modules/@babel/runtime": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
- "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
+ "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
"dependencies": {
- "regenerator-runtime": "^0.13.11"
+ "regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/runtime/node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ },
"node_modules/@babel/template": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
@@ -5274,6 +5281,21 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
+ "node_modules/bootstrap-icons": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
+ "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ]
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -8149,6 +8171,30 @@
"url": "https://www.patreon.com/infusion"
}
},
+ "node_modules/framer-motion": {
+ "version": "11.2.4",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.4.tgz",
+ "integrity": "sha512-D+EXd0lspaZijv3BJhAcSsyGz+gnvoEdnf+QWkPZdhoFzbeX/2skrH9XSVFb0osgUnCajW8x1frjhLuKwa/Reg==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -18369,11 +18415,18 @@
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
},
"@babel/runtime": {
- "version": "7.21.0",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
- "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
+ "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
"requires": {
- "regenerator-runtime": "^0.13.11"
+ "regenerator-runtime": "^0.14.0"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ }
}
},
"@babel/template": {
@@ -20871,6 +20924,11 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
+ "bootstrap-icons": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
+ "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww=="
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -22961,6 +23019,14 @@
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA=="
},
+ "framer-motion": {
+ "version": "11.2.4",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.4.tgz",
+ "integrity": "sha512-D+EXd0lspaZijv3BJhAcSsyGz+gnvoEdnf+QWkPZdhoFzbeX/2skrH9XSVFb0osgUnCajW8x1frjhLuKwa/Reg==",
+ "requires": {
+ "tslib": "^2.4.0"
+ }
+ },
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
diff --git a/package.json b/package.json
index 47e35c6..6d52a58 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"@types/node": "^16.18.14",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
+ "bootstrap-icons": "^1.11.3",
+ "framer-motion": "^11.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-query": "^3.39.3",
diff --git a/src/App.tsx b/src/App.tsx
index bf8e750..b4c0d18 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,14 +1,11 @@
import React from 'react';
import '../src/styles/App.scss';
import Router from "./config/Router";
-import {useTheme} from "./utils/contexts/ThemeContext";
function App() {
- const {isDarkMode} = useTheme();
-
return (
-
+
);
diff --git a/src/layout/components/AnimatedDiv.tsx b/src/layout/components/AnimatedDiv.tsx
new file mode 100644
index 0000000..37ea5c5
--- /dev/null
+++ b/src/layout/components/AnimatedDiv.tsx
@@ -0,0 +1,38 @@
+import {FunctionComponent} from "react";
+import {motion} from "framer-motion";
+
+interface Props {
+ animation?: any;
+ className?: string;
+ children: any;
+}
+
+const defaultAnimation = {
+ initial: {
+ opacity: 0
+ },
+ animate: {
+ opacity: 1
+ },
+ exit: {
+ opacity: 0,
+ transition: {
+ duration: .1,
+ ease: 'easeInOut'
+ }
+ }
+}
+const AnimatedDiv: FunctionComponent
= ({animation, className, children}) => {
+ return (
+
+ {children}
+
+ )
+}
+
+export default AnimatedDiv;
\ No newline at end of file
diff --git a/src/layout/components/Button.tsx b/src/layout/components/Button.tsx
index 370533f..22e3aa4 100644
--- a/src/layout/components/Button.tsx
+++ b/src/layout/components/Button.tsx
@@ -4,20 +4,23 @@ interface ButtonProps extends DetailedHTMLProps = (props) => {
-
- const {className, ...rest} = props;
-
- return (
-
- );
-}
-
export const NoButton: FunctionComponent = (props) => {
const {className, ...rest} = props;
return (
-
+
);
+}
+
+export const Button: FunctionComponent = (props) => {
+ const {className, ...rest} = props;
+
+ return (
+
+ );
+}
+
+const getClassName = (templateClass: string, otherClass?: string) => {
+ return templateClass + (otherClass ? ` ${otherClass}` : "");
}
\ No newline at end of file
diff --git a/src/layout/components/Card.tsx b/src/layout/components/Card.tsx
index 28001ef..4a99fea 100644
--- a/src/layout/components/Card.tsx
+++ b/src/layout/components/Card.tsx
@@ -1,13 +1,13 @@
import React, {FunctionComponent} from "react";
+import {NoButton} from "./Button";
interface Props {
cardString: string;
handleClick?: (cardString: string) => void;
isHidden?: boolean;
- isClickable?: boolean;
}
-const Card: FunctionComponent = ({cardString, handleClick, isHidden, isClickable}) => {
+const Card: FunctionComponent = ({cardString, handleClick, isHidden}) => {
const cardType = cardString.split(' ')[0].toLowerCase();
const cardValue = cardString.split(' ')[1].toLowerCase();
@@ -30,9 +30,17 @@ const Card: FunctionComponent = ({cardString, handleClick, isHidden, isCl
}
}
+ if (handleClick) {
+ return (
+
+
+
+ )
+ }
+
return (
-
-
})
+
+
)
}
diff --git a/src/layout/components/Chat.tsx b/src/layout/components/Chat.tsx
index bc5182c..cd025c6 100644
--- a/src/layout/components/Chat.tsx
+++ b/src/layout/components/Chat.tsx
@@ -1,7 +1,7 @@
-import {GHButton} from "./Button";
import React, {FormEvent, FunctionComponent, RefObject} from "react";
+import {NoButton} from "./Button";
-interface Props{
+interface Props {
chatRef: RefObject
;
handleSend: (message: string) => void;
}
@@ -12,7 +12,7 @@ const Chat: FunctionComponent = ({chatRef, handleSend}) => {
form.preventDefault();
const data = new FormData(form.currentTarget);
const chatInput = data.get("chat-input") as string;
- if(!chatInput) return;
+ if (!chatInput) return;
handleSend(chatInput);
form.currentTarget.reset();
}
@@ -21,8 +21,11 @@ const Chat: FunctionComponent = ({chatRef, handleSend}) => {
<>
>
)
diff --git a/src/layout/components/Deck.tsx b/src/layout/components/Deck.tsx
index a7419ac..f0fbff9 100644
--- a/src/layout/components/Deck.tsx
+++ b/src/layout/components/Deck.tsx
@@ -15,12 +15,10 @@ const Deck: FunctionComponent = ({currentCard, actionOnClick}) => {
return (
);
diff --git a/src/layout/components/Hand.tsx b/src/layout/components/Hand.tsx
index 32c05a6..d9a8869 100644
--- a/src/layout/components/Hand.tsx
+++ b/src/layout/components/Hand.tsx
@@ -3,25 +3,24 @@ import Card from "./Card";
interface Props {
hand: string[];
- actionOnClick: (cardString: string) => void;
+ actionOnClick?: (cardString: string) => void;
isHidden?: boolean;
}
const Hand: FunctionComponent = ({hand, actionOnClick, isHidden}) => {
- const isMyHand = !isHidden;
-
return (
-
+
{
hand.map((card, index) => {
return (
-
+ -
+
+
)
})
}
-
+
)
}
diff --git a/src/layout/components/Lobby.tsx b/src/layout/components/Lobby.tsx
deleted file mode 100644
index 3dfd788..0000000
--- a/src/layout/components/Lobby.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import {FunctionComponent} from "react";
-import {GHButton} from "./Button";
-
-interface Props {
- winner: string | undefined;
- handleLobbyAction: (action: string) => void;
-}
-
-const Lobby: FunctionComponent = ({winner, handleLobbyAction}) => {
-
- const onStartClick = () => {
- console.log('Start Game');
- handleLobbyAction("START");
- }
-
- // @ts-ignore
- return (
-
-
Lobby
- {
- winner &&
- {winner} has won the game!
- }
- Start Game
-
- );
-}
-
-export default Lobby;
diff --git a/src/layout/components/Modal.tsx b/src/layout/components/Modal.tsx
new file mode 100644
index 0000000..eb43862
--- /dev/null
+++ b/src/layout/components/Modal.tsx
@@ -0,0 +1,25 @@
+import {FunctionComponent} from "react";
+import {createPortal} from "react-dom";
+import AnimatedDiv from "./AnimatedDiv";
+
+interface Props {
+ isOpen: boolean
+ children: JSX.Element | JSX.Element[];
+ modalRoot: HTMLElement | null;
+}
+
+const Modal: FunctionComponent = (props) => {
+
+ return (
+ <>
+ {
+ props.isOpen &&
+ createPortal(
+ {props.children}
+ , props.modalRoot ?? document.body)
+ }
+ >
+ )
+}
+
+export default Modal;
\ No newline at end of file
diff --git a/src/layout/pages/MainLobby.tsx b/src/layout/pages/MainLobby.tsx
index 006e532..2bd1331 100644
--- a/src/layout/pages/MainLobby.tsx
+++ b/src/layout/pages/MainLobby.tsx
@@ -37,11 +37,11 @@ const MainLobby = () => {
Host Game
-
+
navigateTo('/rooms')}>
Join Game
-
+
diff --git a/src/layout/pages/Room.tsx b/src/layout/pages/Room.tsx
index 7b4a7f3..40fdaa4 100644
--- a/src/layout/pages/Room.tsx
+++ b/src/layout/pages/Room.tsx
@@ -1,25 +1,23 @@
import useWebSocket from "react-use-websocket";
import React, {useEffect} from "react";
import {useNavigate, useParams} from "react-router";
-import {GHButton} from "../components/Button";
import useTitle from "../../utils/hooks/TitleHook";
-import Game, {GameState} from "../components/Game";
-import Lobby from "../components/Lobby";
+import Game, {GameState} from "../views/Game/Game";
+import Lobby from "../views/Lobby/Lobby";
import Chat from "../components/Chat";
+import {Button} from "../components/Button";
-export interface Player {
- Name: string;
+export interface Connection {
Id: string;
+ Name: string;
+}
+
+export interface Player extends Connection {
CardsLeft: number;
}
-interface SocketMessage {
+export interface SocketMessage {
Type: string;
- Payload: any;
-}
-
-export interface GameAction {
- Action: string;
Data: string;
}
@@ -31,12 +29,12 @@ const Room = () => {
const {roomId} = useParams();
- const playerName = localStorage.getItem('playerName') ?? Math.random().toString(36).substring(7);
+ const playerName = localStorage.getItem('playerName') ?? "";
const WS_URL = `${process.env.REACT_APP_WEBSOCKET_URL}/room/${roomId}/${playerName}`;
const [gameState, setGameState] = React.useState(undefined);
- const [lobbyState, setLobbyState] = React.useState("");
const [winner, setWinner] = React.useState(undefined);
+ const [playerList, setPlayerList] = React.useState([]);
const chatRef = React.useRef(null);
@@ -59,15 +57,11 @@ const Room = () => {
onOpen: () => {
console.log('WebSocket connection established.');
},
- onMessage: (event) => {
- const data = JSON.parse(event.data);
- const payload = JSON.parse(data.Payload);
+ onMessage: (messageEvent) => {
+ const data: SocketMessage = JSON.parse(messageEvent.data);
+ const payload = JSON.parse(data.Data);
switch (data.Type) {
- case 'LOBBY':
- setLobbyState(data.Type);
- break;
case 'GAME':
- setLobbyState(data.Type);
setGameState(payload);
break;
case 'CHAT':
@@ -77,12 +71,32 @@ const Room = () => {
setWinner(payload.Name)
setGameState(undefined);
break;
+ case "JOIN":
+ onJoin(payload);
+ break;
+ case "LEAVE":
+ onLeave(payload.Connection);
+ break;
default:
console.log('Unknown message type:', data.Type);
}
+ },
+ onClose: (closeEvent) => {
+ navigateTo('/');
}
});
+ const onJoin = (payload: { Connections: Connection[], NewConnection: Connection }) => {
+ setPlayerList(payload.Connections);
+ addChatMessage({Sender: "System", Message: payload.NewConnection.Name + " has joined the room."});
+ }
+
+ const onLeave = (payload: Connection) => {
+ const connection = payload;
+ setPlayerList(playerList.filter(player => player.Id !== connection.Id));
+ addChatMessage({Sender: "System", Message: connection.Name + " has left the room."});
+ }
+
const handleLeaveRoom = () => {
navigateTo('/');
}
@@ -91,18 +105,18 @@ const Room = () => {
websocket.sendMessage(JSON.stringify(message));
}
- const handleGameAction = (action: GameAction) => {
- handleSend({Type: "GAME", Payload: JSON.stringify(action)});
+ const handleLobbyAction = (action: string) => {
+ handleSend({Type: "LOBBY", Data: JSON.stringify(action)});
}
- const handleLobbyAction = (action: string) => {
- handleSend({Type: "LOBBY", Payload: JSON.stringify(action)});
+ const handleKick = (playerId: string) => {
+ handleSend({Type: "KICK", Data: playerId});
}
const handleChatMessage = (chatMessage: string) => {
handleSend({
Type: "CHAT",
- Payload: chatMessage
+ Data: chatMessage
});
}
@@ -118,23 +132,24 @@ const Room = () => {
return (
-
- {
- gameState ?
-
- : lobbyState === 'LOBBY' &&
-
- }
-
+ {
+ gameState ?
+
+ :
+
+ }
)
}
diff --git a/src/layout/pages/Rooms.tsx b/src/layout/pages/Rooms.tsx
index b5bdbc3..6f65707 100644
--- a/src/layout/pages/Rooms.tsx
+++ b/src/layout/pages/Rooms.tsx
@@ -1,6 +1,7 @@
import useTitle from "../../utils/hooks/TitleHook";
import React from "react";
import {useNavigate} from "react-router";
+import {NoButton} from "../components/Button";
const ROOM_URL = `${process.env.REACT_APP_API_URL}/room`;
@@ -11,6 +12,13 @@ const Rooms = () => {
const [rooms, setRooms] = React.useState([]);
+ const handleRoomClick = (room: string) => {
+ if (!localStorage.getItem('playerName')){
+ localStorage.setItem("playerName", "Mau");
+ }
+ navigateTo(`/room/${room}`);
+ }
+
React.useEffect(() => {
fetch(ROOM_URL)
.then(r => r.json())
@@ -22,9 +30,10 @@ const Rooms = () => {
Rooms
{
- rooms.map((room, index) => {
- return - navigateTo(`/room/${room}`)}>{room}
+ rooms.map((room) => {
+ return -
+ handleRoomClick(room)}>{room}
+
})
}
diff --git a/src/layout/components/Game.tsx b/src/layout/views/Game/Game.tsx
similarity index 65%
rename from src/layout/components/Game.tsx
rename to src/layout/views/Game/Game.tsx
index 206dcb1..62f3c4b 100644
--- a/src/layout/components/Game.tsx
+++ b/src/layout/views/Game/Game.tsx
@@ -1,44 +1,45 @@
import React, {FunctionComponent} from "react";
-import Deck from "./Deck";
-import Hand from "./Hand";
-import {GHButton} from "./Button";
-import {GameAction, Player} from "../pages/Room";
+import {Player, SocketMessage} from "../../pages/Room";
+import Deck from "../../components/Deck";
+import Hand from "../../components/Hand";
+import CardTypeChoiceModal from "./components/CardTypeChoiceModal";
export interface GameState {
Me: Player;
- CurrentState: string;
+ MyState: string;
Hand: string[];
CurrentCard: string;
+ NextAllowedCardType?: string
CurrentPlayer: Player;
Players: Player[];
}
interface Props {
gameState: GameState
- handleGameAction: (action: GameAction) => void;
+ handleGameAction: (message: SocketMessage) => void;
}
-const CHOICES = ['SPADES', 'HEARTS', 'DIAMONDS', 'CLUBS'];
-
const Game: FunctionComponent = ({gameState, handleGameAction}) => {
const handleChoice = (choice: string) => {
- handleGameAction({Action: 'CHOOSE', Data: choice});
+ handleGameAction({Type: 'CHOOSE', Data: choice});
}
const handleDraw = () => {
- handleGameAction({Action: 'DRAW', Data: ""});
+ handleGameAction({Type: 'DRAW', Data: ""});
}
const handleCardSend = (cardString: string) => {
- handleGameAction({Action: "PLAYCARD", Data: JSON.stringify({
- CardType: cardString.split(' ')[0],
- CardValue: cardString.split(' ')[1]
- })})
+ handleGameAction({
+ Type: "PLAY", Data: JSON.stringify({
+ CardType: cardString.split(' ')[0],
+ CardValue: cardString.split(' ')[1]
+ })
+ })
}
return (
-
+
{
gameState?.Me?.Id &&
@@ -61,6 +62,10 @@ const Game: FunctionComponent = ({gameState, handleGameAction}) => {
})
}
+ {
+ gameState.NextAllowedCardType &&
+ Next card type: {gameState.NextAllowedCardType}
+ }
{
gameState.CurrentCard &&
@@ -70,10 +75,9 @@ const Game: FunctionComponent = ({gameState, handleGameAction}) => {
}
{
- gameState.CurrentState === 'CHOOSE' &&
- CHOICES.map(choice => handleChoice(choice)}>{choice})
+
}
-
+
)
}
diff --git a/src/layout/views/Game/components/CardTypeChoiceModal.tsx b/src/layout/views/Game/components/CardTypeChoiceModal.tsx
new file mode 100644
index 0000000..18b3eaa
--- /dev/null
+++ b/src/layout/views/Game/components/CardTypeChoiceModal.tsx
@@ -0,0 +1,41 @@
+import React, {FunctionComponent} from "react";
+import {NoButton} from "../../../components/Button";
+import Modal from "../../../components/Modal";
+
+export interface Choice {
+ name: string;
+ icon: string;
+}
+
+interface Props {
+ isOpen: boolean;
+ handleChoice: (choice: string) => void;
+}
+
+const cardTypeChoices: Choice[] = [
+ {name: 'SPADES', icon: "bi-suit-spade-fill"},
+ {name: 'HEARTS', icon: "bi-suit-heart-fill"},
+ {name: 'DIAMONDS', icon: "bi-suit-diamond-fill"},
+ {name: 'CLUBS', icon: "bi-suit-club-fill"}
+];
+
+const CardTypeChoiceModal: FunctionComponent = (props) => {
+ return (
+
+
+
+ {
+ cardTypeChoices.map(type =>
+ props.handleChoice(type.name)} aria-label={type.name.charAt(0) + type.name.substring(1).toLowerCase()}>
+
+
+ )
+ }
+
+
Choose a card type
+
+
+ )
+}
+
+export default CardTypeChoiceModal;
\ No newline at end of file
diff --git a/src/layout/views/Lobby/Lobby.tsx b/src/layout/views/Lobby/Lobby.tsx
new file mode 100644
index 0000000..a21245a
--- /dev/null
+++ b/src/layout/views/Lobby/Lobby.tsx
@@ -0,0 +1,46 @@
+import {FunctionComponent} from "react";
+import {Connection} from "../../pages/Room";
+import {Button} from "../../components/Button";
+
+interface Props {
+ winner: string | undefined;
+ handleLobbyAction: (action: string) => void;
+ playerList: Connection[];
+ onKickClick: (playerId: string) => void;
+}
+
+const Lobby: FunctionComponent = ({winner, handleLobbyAction, playerList, onKickClick}) => {
+
+ const onStartClick = () => {
+ console.log('Start Game');
+ handleLobbyAction("START");
+ }
+
+ return (
+
+ Waiting for host to start the game
+
+ Players
+
+ {
+ playerList.map((player) => {
+ return -
+
{player.Name}
+
+
+ })
+ }
+
+
+
+ Game Settings
+ Not available... yet?
+
+
+
+ );
+}
+
+export default Lobby;
diff --git a/src/styles/App.scss b/src/styles/App.scss
index 91b4b2e..3e4f11e 100644
--- a/src/styles/App.scss
+++ b/src/styles/App.scss
@@ -12,17 +12,30 @@
}
body {
+ position: relative;
margin: 0;
+ overflow: hidden;
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
height: 100dvh;
width: 100dvw;
- overflow: hidden;
+ background-color: #131313;
+ color: #ffffff;
}
#root, .app {
- background-color: #131313;
- color: #ffffff;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+}
+
+.app {
padding: 1rem;
height: 100%;
+ width: 100%;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ margin: 0;
}
.mau {
diff --git a/src/styles/layout/components/_components.scss b/src/styles/layout/components/_components.scss
index 55c56f4..8913337 100644
--- a/src/styles/layout/components/_components.scss
+++ b/src/styles/layout/components/_components.scss
@@ -1,6 +1,10 @@
@import "button";
@import "card";
@import "chat";
-@import "hand";
+@import "card-type-choice-modal";
+@import "deck";
@import "game";
-@import "deck";
\ No newline at end of file
+@import "game-lobby";
+@import "hand";
+@import "modal";
+@import "room-header";
\ No newline at end of file
diff --git a/src/styles/layout/components/button.scss b/src/styles/layout/components/button.scss
index d3fc8c8..4d4796d 100644
--- a/src/styles/layout/components/button.scss
+++ b/src/styles/layout/components/button.scss
@@ -1,58 +1,11 @@
-.gh-button {
- appearance: none;
- background-color: #FAFBFC;
- border: 1px solid rgba(27, 31, 35, 0.15);
- border-radius: 6px;
- box-shadow: rgba(27, 31, 35, 0.04) 0 1px 0, rgba(255, 255, 255, 0.25) 0 1px 0 inset;
- box-sizing: border-box;
- color: #24292E;
- cursor: pointer;
- display: inline-block;
- font-family: -apple-system, system-ui, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
- font-size: 14px;
- font-weight: 500;
- line-height: 20px;
- list-style: none;
- padding: 6px 16px;
- position: relative;
- transition: background-color 0.2s cubic-bezier(0.3, 0, 0.5, 1);
- user-select: none;
- -webkit-user-select: none;
- touch-action: manipulation;
- vertical-align: middle;
- white-space: nowrap;
- word-wrap: break-word;
+.icon-end {
+ display: grid;
+ grid-template-columns: 1fr auto;
+}
- :hover {
- background-color: #F3F4F6;
- text-decoration: none;
- transition-duration: 0.1s;
- }
-
- :disabled {
- background-color: #FAFBFC;
- border-color: rgba(27, 31, 35, 0.15);
- color: #959DA5;
- cursor: default;
- }
-
- :active {
- background-color: #EDEFF2;
- box-shadow: rgba(225, 228, 232, 0.2) 0 1px 0 inset;
- transition: none 0s;
- }
-
- :focus {
- outline: 1px transparent;
- }
-
- :before {
- display: none;
- }
-
- ::-webkit-details-marker {
- display: none;
- }
+.icon-start {
+ display: grid;
+ grid-template-columns: auto 1fr;
}
.no-button {
@@ -60,4 +13,34 @@
border: none;
color: inherit;
cursor: pointer;
+}
+
+.button {
+ background-color: #2e2e2e;
+ border: none;
+ color: #fff;
+ font-size: 1rem;
+ padding: 1rem;
+ border-radius: 0.5rem;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #3e3e3e;
+ }
+
+ &-success {
+ background-color: #276b00;
+
+ &:hover {
+ background-color: #3e8e00;
+ }
+ }
+
+ &-danger {
+ background-color: #6b0000;
+
+ &:hover {
+ background-color: #8e0000;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/styles/layout/components/card-type-choice-modal.scss b/src/styles/layout/components/card-type-choice-modal.scss
new file mode 100644
index 0000000..e598623
--- /dev/null
+++ b/src/styles/layout/components/card-type-choice-modal.scss
@@ -0,0 +1,17 @@
+.card-type-choice-modal {
+ background-color: #9f7d2f;
+ border-radius: 0.5rem;
+ padding: 1rem 2rem;
+
+ &__choices {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: space-between;
+ font-size: 3rem;
+ }
+
+ &__title {
+ text-align: center;
+ margin-top: 1rem;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/layout/components/card.scss b/src/styles/layout/components/card.scss
index f2e8439..d96a750 100644
--- a/src/styles/layout/components/card.scss
+++ b/src/styles/layout/components/card.scss
@@ -2,10 +2,6 @@
width: 100px;
min-width: 100px;
- &-clickable {
- cursor: pointer;
- }
-
&__texture {
width: 100%;
height: 100%;
diff --git a/src/styles/layout/components/chat.scss b/src/styles/layout/components/chat.scss
index 732b436..6f3ab3e 100644
--- a/src/styles/layout/components/chat.scss
+++ b/src/styles/layout/components/chat.scss
@@ -17,12 +17,22 @@
flex-direction: column;
&__input {
+ display: grid;
+ grid-template-columns: 1fr auto;
background-color: #2e2e2e;
border: none;
color: #fff;
font-size: 1rem;
padding: 0.5rem;
border-radius: 0.5rem;
+
+ > input {
+ background-color: transparent;
+ border: none;
+ color: #fff;
+ font-size: 1rem;
+ outline: none;
+ }
}
}
}
\ No newline at end of file
diff --git a/src/styles/layout/components/game-lobby.scss b/src/styles/layout/components/game-lobby.scss
new file mode 100644
index 0000000..65cb2b4
--- /dev/null
+++ b/src/styles/layout/components/game-lobby.scss
@@ -0,0 +1,41 @@
+.game-lobby {
+ display: grid;
+ grid-template-rows: auto 1fr 1fr auto;
+ grid-row-gap: 1rem;
+ justify-items: center;
+
+ > * {
+ width: 100%;
+ }
+
+ &__player-list {
+ overflow-y: hidden;
+ padding: 0;
+ list-style: none;
+ min-height: 90%;
+ max-height: 90%;
+ height: 0;
+
+ &:hover {
+ overflow-y: scroll;
+ }
+
+ li {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 0.5rem;
+ background-color: #2e2e2e;
+ border-radius: 0.5rem;
+ padding: 0.5rem;
+ margin: 0.5rem 0;
+
+ button {
+ padding: 0 1rem;
+ }
+ }
+ }
+
+ &__start-game {
+ font-size: 1rem;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/layout/components/game.scss b/src/styles/layout/components/game.scss
index 3737ee5..0557b5b 100644
--- a/src/styles/layout/components/game.scss
+++ b/src/styles/layout/components/game.scss
@@ -3,6 +3,7 @@
display: grid;
grid-template-rows: 1fr 1fr 1fr;
place-items: center;
+ position: relative;
&__players {
list-style-type: none;
diff --git a/src/styles/layout/components/hand.scss b/src/styles/layout/components/hand.scss
index 1651805..a68aaa1 100644
--- a/src/styles/layout/components/hand.scss
+++ b/src/styles/layout/components/hand.scss
@@ -5,4 +5,8 @@
overflow-x: scroll;
padding: 0.5rem;
gap: 0.5rem;
+
+ &__item {
+ list-style: none;
+ }
}
diff --git a/src/styles/layout/components/modal.scss b/src/styles/layout/components/modal.scss
new file mode 100644
index 0000000..4a36317
--- /dev/null
+++ b/src/styles/layout/components/modal.scss
@@ -0,0 +1,13 @@
+.modal {
+ &-root {
+ position: absolute;
+ padding: 1rem;
+ height: 100%;
+ width: 100%;
+ background-color: rgba(0, 0, 0, 0.25);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ transition: all 0.5s ease-in-out;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/layout/components/room-header.scss b/src/styles/layout/components/room-header.scss
new file mode 100644
index 0000000..1479d05
--- /dev/null
+++ b/src/styles/layout/components/room-header.scss
@@ -0,0 +1,10 @@
+.aside-header {
+
+ &__titles {
+ margin-bottom: 1rem;
+
+ > * {
+ margin: 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/styles/layout/pages/room.scss b/src/styles/layout/pages/room.scss
index 5c11822..990d5d3 100644
--- a/src/styles/layout/pages/room.scss
+++ b/src/styles/layout/pages/room.scss
@@ -1,6 +1,7 @@
.room {
display: grid;
grid-template-columns: auto 1fr;
+ grid-column-gap: 1rem;
height: 100%;
&-aside {
@@ -8,5 +9,11 @@
min-width: 20%;
grid-template-rows: auto minmax(0, 1fr) auto;
overflow: hidden auto;
+
+ button {
+ padding: 0.5rem;
+ font-size: 1rem;
+ width: 100%;
+ }
}
}
\ No newline at end of file
diff --git a/src/styles/vendor/_vendor.scss b/src/styles/vendor/_vendor.scss
index c1f8270..146a141 100644
--- a/src/styles/vendor/_vendor.scss
+++ b/src/styles/vendor/_vendor.scss
@@ -1 +1,2 @@
+@import "bootstrap";
@import "normalize";
\ No newline at end of file
diff --git a/src/styles/vendor/bootstrap.scss b/src/styles/vendor/bootstrap.scss
new file mode 100644
index 0000000..7673e3e
--- /dev/null
+++ b/src/styles/vendor/bootstrap.scss
@@ -0,0 +1,2 @@
+//@import "../../../node_modules/bootstrap/scss/bootstrap.scss";
+@import "../../../node_modules/bootstrap-icons/font/bootstrap-icons.scss";
\ No newline at end of file