Updated front-end to match back-end and better styling
This commit is contained in:
38
src/layout/components/AnimatedDiv.tsx
Normal file
38
src/layout/components/AnimatedDiv.tsx
Normal 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;
|
||||
@@ -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}` : "");
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
25
src/layout/components/Modal.tsx
Normal file
25
src/layout/components/Modal.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user