Updated front-end to match back-end and better styling
Some checks failed
Build Mau & Deploy Mau / build (push) Successful in 1m48s
Build Mau & Deploy Mau / deploy (push) Has been cancelled
Build Mau & Deploy Mau / build (pull_request) Successful in 1m37s
Build Mau & Deploy Mau / deploy (pull_request) Failing after 2m3s

This commit is contained in:
DTieman
2024-05-19 23:50:47 +02:00
parent 4ae1538552
commit 6ba1523853
25 changed files with 481 additions and 155 deletions

View File

@@ -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<Props> = ({animation, className, children}) => {
return (
<motion.div
variants={animation ?? defaultAnimation}
initial={"initial"}
animate={"animate"}
exit={"exit"}
className={className}>
{children}
</motion.div>
)
}
export default AnimatedDiv;

View File

@@ -4,20 +4,23 @@ interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonE
}
export const GHButton: FunctionComponent<ButtonProps> = (props) => {
const {className, ...rest} = props;
return (
<button className={`gh-button ${className}`} {...rest}></button>
);
}
export const NoButton: FunctionComponent<ButtonProps> = (props) => {
const {className, ...rest} = props;
return (
<button className={`no-button ${className}`} {...rest}></button>
<button className={getClassName("no-button", className)} {...rest}/>
);
}
export const Button: FunctionComponent<ButtonProps> = (props) => {
const {className, ...rest} = props;
return (
<button className={getClassName("button", className)} {...rest}/>
);
}
const getClassName = (templateClass: string, otherClass?: string) => {
return templateClass + (otherClass ? ` ${otherClass}` : "");
}

View File

@@ -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<HTMLOListElement>;
handleSend: (message: string) => void;
}
@@ -12,7 +12,7 @@ const Chat: FunctionComponent<Props> = ({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<Props> = ({chatRef, handleSend}) => {
<>
<ol className={"chat__list"} ref={chatRef}/>
<form className={"chat-form"} onSubmit={handleChat}>
<input className={"chat-form__input"} type="text" placeholder={"Chat here..."} id={"chat-input"} name={"chat-input"}/>
<GHButton type={"submit"}>Send</GHButton>
<label className={"chat-form__input"}>
<input type={"text"} placeholder={"Chat here..."} id={"chat-input"} name={"chat-input"}
autoComplete={"off"}/>
<NoButton type={"submit"} className={"bi bi-send"}/>
</label>
</form>
</>
)

View File

@@ -1,80 +0,0 @@
import React, {FunctionComponent} from "react";
import Deck from "./Deck";
import Hand from "./Hand";
import {GHButton} from "./Button";
import {Player, SocketMessage} from "../pages/Room";
export interface GameState {
Me: Player;
MyState: string;
Hand: string[];
CurrentCard: string;
CurrentPlayer: Player;
Players: Player[];
}
interface Props {
gameState: GameState
handleGameAction: (message: SocketMessage) => void;
}
const CHOICES = ['SPADES', 'HEARTS', 'DIAMONDS', 'CLUBS'];
const Game: FunctionComponent<Props> = ({gameState, handleGameAction}) => {
const handleChoice = (choice: string) => {
handleGameAction({Type: 'CHOOSE', Data: choice});
}
const handleDraw = () => {
handleGameAction({Type: 'DRAW', Data: ""});
}
const handleCardSend = (cardString: string) => {
handleGameAction({Type: "PLAY", Data: JSON.stringify({
CardType: cardString.split(' ')[0],
CardValue: cardString.split(' ')[1]
})})
}
return (
<div className="game">
<ol className={"game__players"}>
{
gameState?.Me?.Id &&
gameState.Players.map((player) => {
const isCurrentPlayer = player.Id === gameState.CurrentPlayer.Id;
const isMe = player.Id === gameState.Me.Id;
const style =
{
fontWeight: isMe ? 'bold' : 'normal',
outline: isCurrentPlayer ? '2px solid red' : 'none'
}
return <li key={player.Id} style={style}>
<div className={"game__players__info"}>
<h3>{player.Name} {isMe && '(You)'}</h3>
<p>{player.CardsLeft} left</p>
</div>
</li>
})
}
</ol>
{
gameState.CurrentCard &&
<Deck currentCard={gameState.CurrentCard} actionOnClick={handleDraw}/>
}
{
gameState.Hand &&
<Hand hand={gameState.Hand} actionOnClick={handleCardSend}/>
}
{
gameState.MyState === 'CHOOSE' &&
CHOICES.map(choice => <GHButton key={choice} onClick={() => handleChoice(choice)}>{choice}</GHButton>)
}
</div>
)
}
export default Game;

View File

@@ -1,29 +0,0 @@
import {FunctionComponent} from "react";
import {GHButton} from "./Button";
interface Props {
winner: string | undefined;
handleLobbyAction: (action: string) => void;
}
const Lobby: FunctionComponent<Props> = ({winner, handleLobbyAction}) => {
const onStartClick = () => {
console.log('Start Game');
handleLobbyAction("START");
}
// @ts-ignore
return (
<div>
<h1>Lobby</h1>
{
winner &&
<h2>{winner} has won the game!</h2>
}
<GHButton onClick={onStartClick}>Start Game</GHButton>
</div>
);
}
export default Lobby;

View File

@@ -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> = (props) => {
return (
<>
{
props.isOpen &&
createPortal(<AnimatedDiv className={"modal-root"}>
<div className={"modal-container"}>{props.children}</div>
</AnimatedDiv>, props.modalRoot ?? document.body)
}
</>
)
}
export default Modal;