From 4ae15385521a542971b6b9cad2d27fd24e2d1759 Mon Sep 17 00:00:00 2001 From: DTieman Date: Sun, 5 May 2024 17:08:20 +0200 Subject: [PATCH 1/2] Updated front-end to match back-end rewrite --- src/layout/components/Card.tsx | 16 ++++++++--- src/layout/components/Deck.tsx | 4 +-- src/layout/components/Game.tsx | 14 +++++----- src/layout/components/Hand.tsx | 13 ++++----- src/layout/pages/MainLobby.tsx | 4 +-- src/layout/pages/Room.tsx | 38 ++++++++++++-------------- src/styles/layout/components/card.scss | 4 --- src/styles/layout/components/hand.scss | 4 +++ 8 files changed, 49 insertions(+), 48 deletions(-) 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 ( + + {cardName}/ + + ) + } + return ( -
- {cardName} +
+ {cardName}/
) } 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/Game.tsx b/src/layout/components/Game.tsx index 206dcb1..d03f14b 100644 --- a/src/layout/components/Game.tsx +++ b/src/layout/components/Game.tsx @@ -2,11 +2,11 @@ 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"; export interface GameState { Me: Player; - CurrentState: string; + MyState: string; Hand: string[]; CurrentCard: string; CurrentPlayer: Player; @@ -15,7 +15,7 @@ export interface GameState { interface Props { gameState: GameState - handleGameAction: (action: GameAction) => void; + handleGameAction: (message: SocketMessage) => void; } const CHOICES = ['SPADES', 'HEARTS', 'DIAMONDS', 'CLUBS']; @@ -23,15 +23,15 @@ 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({ + handleGameAction({Type: "PLAY", Data: JSON.stringify({ CardType: cardString.split(' ')[0], CardValue: cardString.split(' ')[1] })}) @@ -70,7 +70,7 @@ const Game: FunctionComponent = ({gameState, handleGameAction}) => { } { - gameState.CurrentState === 'CHOOSE' && + gameState.MyState === 'CHOOSE' && CHOICES.map(choice => handleChoice(choice)}>{choice}) }
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/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..7eeb651 100644 --- a/src/layout/pages/Room.tsx +++ b/src/layout/pages/Room.tsx @@ -13,14 +13,13 @@ export interface Player { CardsLeft: number; } -interface SocketMessage { +export interface SocketMessage { Type: string; - Payload: any; + Data: string; } -export interface GameAction { - Action: string; - Data: string; +interface JoinLeaveMessage { + ChatMessage: string; } const Room = () => { @@ -35,7 +34,6 @@ const Room = () => { 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 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,6 +71,12 @@ const Room = () => { setWinner(payload.Name) setGameState(undefined); break; + case "JOIN": + addChatMessage({Sender: "System", Message: (JSON.parse(data.Data) as JoinLeaveMessage).ChatMessage}); + break; + case "LEAVE": + addChatMessage({Sender: "System", Message: (JSON.parse(data.Data) as JoinLeaveMessage).ChatMessage}); + break; default: console.log('Unknown message type:', data.Type); } @@ -91,18 +91,14 @@ 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", Payload: JSON.stringify(action)}); + handleSend({Type: "LOBBY", Data: JSON.stringify(action)}); } const handleChatMessage = (chatMessage: string) => { handleSend({ Type: "CHAT", - Payload: chatMessage + Data: chatMessage }); } @@ -130,8 +126,8 @@ const Room = () => {
{ gameState ? - - : lobbyState === 'LOBBY' && + + : }
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/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; + } } From 6ba1523853bbb5854f66d2ff7d96d110750dc564 Mon Sep 17 00:00:00 2001 From: DTieman Date: Sun, 19 May 2024 23:50:47 +0200 Subject: [PATCH 2/2] Updated front-end to match back-end and better styling --- package-lock.json | 82 +++++++++++++++-- package.json | 2 + src/App.tsx | 5 +- src/layout/components/AnimatedDiv.tsx | 38 ++++++++ src/layout/components/Button.tsx | 23 +++-- src/layout/components/Chat.tsx | 13 ++- src/layout/components/Lobby.tsx | 29 ------ src/layout/components/Modal.tsx | 25 +++++ src/layout/pages/Room.tsx | 65 ++++++++----- src/layout/pages/Rooms.tsx | 15 ++- .../{components => views/Game}/Game.tsx | 32 ++++--- .../Game/components/CardTypeChoiceModal.tsx | 41 +++++++++ src/layout/views/Lobby/Lobby.tsx | 46 ++++++++++ src/styles/App.scss | 19 +++- src/styles/layout/components/_components.scss | 8 +- src/styles/layout/components/button.scss | 91 ++++++++----------- .../components/card-type-choice-modal.scss | 17 ++++ src/styles/layout/components/chat.scss | 10 ++ src/styles/layout/components/game-lobby.scss | 41 +++++++++ src/styles/layout/components/game.scss | 1 + src/styles/layout/components/modal.scss | 13 +++ src/styles/layout/components/room-header.scss | 10 ++ src/styles/layout/pages/room.scss | 7 ++ src/styles/vendor/_vendor.scss | 1 + src/styles/vendor/bootstrap.scss | 2 + 25 files changed, 481 insertions(+), 155 deletions(-) create mode 100644 src/layout/components/AnimatedDiv.tsx delete mode 100644 src/layout/components/Lobby.tsx create mode 100644 src/layout/components/Modal.tsx rename src/layout/{components => views/Game}/Game.tsx (72%) create mode 100644 src/layout/views/Game/components/CardTypeChoiceModal.tsx create mode 100644 src/layout/views/Lobby/Lobby.tsx create mode 100644 src/styles/layout/components/card-type-choice-modal.scss create mode 100644 src/styles/layout/components/game-lobby.scss create mode 100644 src/styles/layout/components/modal.scss create mode 100644 src/styles/layout/components/room-header.scss create mode 100644 src/styles/vendor/bootstrap.scss 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 ( - + -
- { - gameState ? - - : - - } -
+ { + 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 72% rename from src/layout/components/Game.tsx rename to src/layout/views/Game/Game.tsx index d03f14b..62f3c4b 100644 --- a/src/layout/components/Game.tsx +++ b/src/layout/views/Game/Game.tsx @@ -1,14 +1,15 @@ import React, {FunctionComponent} from "react"; -import Deck from "./Deck"; -import Hand from "./Hand"; -import {GHButton} from "./Button"; -import {Player, SocketMessage} 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; MyState: string; Hand: string[]; CurrentCard: string; + NextAllowedCardType?: string CurrentPlayer: Player; Players: Player[]; } @@ -18,8 +19,6 @@ interface Props { handleGameAction: (message: SocketMessage) => void; } -const CHOICES = ['SPADES', 'HEARTS', 'DIAMONDS', 'CLUBS']; - const Game: FunctionComponent = ({gameState, handleGameAction}) => { const handleChoice = (choice: string) => { @@ -31,14 +30,16 @@ const Game: FunctionComponent = ({gameState, handleGameAction}) => { } const handleCardSend = (cardString: string) => { - handleGameAction({Type: "PLAY", 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.MyState === '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
  1. +

    {player.Name}

    + +
  2. + }) + } +
+
+
+

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/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/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