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

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