Add mockup create & join forms
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Has been cancelled
Some checks failed
Publish Docker image / Push Docker image to Docker Hub (push) Has been cancelled
This commit is contained in:
parent
d0d999a8db
commit
33f27a278d
12
client/src/common/components/FormInput/FormInput.jsx
Normal file
12
client/src/common/components/FormInput/FormInput.jsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const FormInput = ({
|
||||||
|
type = "text", placeholder, value, onChange,
|
||||||
|
error, maxLength, className = ""
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="input-group">
|
||||||
|
<input type={type} placeholder={placeholder} value={value} onChange={onChange}
|
||||||
|
className={`${error ? "error" : ""} ${className}`} maxLength={maxLength}/>
|
||||||
|
{error && <div className="error-message">{error}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
1
client/src/common/components/FormInput/index.js
Normal file
1
client/src/common/components/FormInput/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {FormInput as default} from "./FormInput";
|
157
client/src/common/styles/forms.sass
Normal file
157
client/src/common/styles/forms.sass
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
@import "@/common/styles/colors"
|
||||||
|
|
||||||
|
.form-base
|
||||||
|
background: rgba(255, 255, 255, 0.1)
|
||||||
|
backdrop-filter: blur(15px)
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2)
|
||||||
|
border-radius: 20px
|
||||||
|
padding: 2.5rem
|
||||||
|
width: 100%
|
||||||
|
position: relative
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15), 0 0 20px rgba(255, 255, 255, 0.1)
|
||||||
|
|
||||||
|
h2
|
||||||
|
margin: 0 0 2rem
|
||||||
|
text-align: center
|
||||||
|
color: $white
|
||||||
|
font-size: 2.5rem
|
||||||
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.4)
|
||||||
|
|
||||||
|
form
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: 1.5rem
|
||||||
|
|
||||||
|
.input-group
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
input
|
||||||
|
width: 100%
|
||||||
|
padding: 1.2rem 1.5rem
|
||||||
|
background: rgba(255, 255, 255, 0.07)
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1)
|
||||||
|
border-radius: 12px
|
||||||
|
color: $white
|
||||||
|
font-family: 'Bangers', sans-serif
|
||||||
|
font-size: 1.2rem
|
||||||
|
letter-spacing: 0.15rem
|
||||||
|
transition: all 0.3s ease
|
||||||
|
outline: none
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
|
&::placeholder
|
||||||
|
color: rgba(255, 255, 255, 0.5)
|
||||||
|
|
||||||
|
&:focus
|
||||||
|
border-color: rgba(255, 255, 255, 0.5)
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15), 0 0 10px rgba(255, 255, 255, 0.2)
|
||||||
|
|
||||||
|
&.error
|
||||||
|
border-color: rgba(255, 0, 0, 0.5)
|
||||||
|
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both
|
||||||
|
|
||||||
|
.error-message
|
||||||
|
position: absolute
|
||||||
|
color: rgba(255, 100, 100, 0.9)
|
||||||
|
font-size: 0.85rem
|
||||||
|
bottom: -1.2rem
|
||||||
|
left: 0.2rem
|
||||||
|
animation: fade-in 0.3s ease
|
||||||
|
|
||||||
|
.button
|
||||||
|
border: none
|
||||||
|
border-radius: 12px
|
||||||
|
padding: 1.2rem
|
||||||
|
color: $white
|
||||||
|
font-family: 'Bangers', sans-serif
|
||||||
|
font-size: 1.3rem
|
||||||
|
letter-spacing: 0.15rem
|
||||||
|
cursor: pointer
|
||||||
|
transition: all 0.3s ease
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
align-items: center
|
||||||
|
gap: 0.8rem
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
transform: translateY(-3px)
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2)
|
||||||
|
|
||||||
|
&:active
|
||||||
|
transform: translateY(0)
|
||||||
|
|
||||||
|
.submit-button
|
||||||
|
@extend .button
|
||||||
|
margin-top: 1rem
|
||||||
|
|
||||||
|
&.join-button
|
||||||
|
background: linear-gradient(45deg, $blue, $purple)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: linear-gradient(45deg, lighten($blue, 10%), lighten($purple, 10%))
|
||||||
|
|
||||||
|
&.create-button
|
||||||
|
background: linear-gradient(45deg, $pink, $purple)
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: linear-gradient(45deg, lighten($pink, 10%), lighten($purple, 10%))
|
||||||
|
|
||||||
|
.back-button
|
||||||
|
position: absolute
|
||||||
|
top: -4rem
|
||||||
|
left: 0
|
||||||
|
background: rgba(255, 255, 255, 0.1)
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2)
|
||||||
|
border-radius: 10px
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
gap: 0.7rem
|
||||||
|
color: rgba(255, 255, 255, 0.9)
|
||||||
|
cursor: pointer
|
||||||
|
font-family: 'Bangers', sans-serif
|
||||||
|
font-size: 1.1rem
|
||||||
|
padding: 0.7rem 1.3rem
|
||||||
|
transition: all 0.3s ease
|
||||||
|
letter-spacing: 0.1rem
|
||||||
|
backdrop-filter: blur(10px)
|
||||||
|
|
||||||
|
svg
|
||||||
|
font-size: 1rem
|
||||||
|
transition: transform 0.3s ease
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color: $white
|
||||||
|
background: rgba(255, 255, 255, 0.2)
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
|
svg
|
||||||
|
transform: translateX(-5px)
|
||||||
|
|
||||||
|
.glassy-card
|
||||||
|
background: rgba(255, 255, 255, 0.07)
|
||||||
|
backdrop-filter: blur(10px)
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2)
|
||||||
|
border-radius: 20px
|
||||||
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1), 0 0 20px rgba(255, 255, 255, 0.1)
|
||||||
|
overflow: hidden
|
||||||
|
transition: all 0.3s ease
|
||||||
|
|
||||||
|
@keyframes shake
|
||||||
|
10%, 90%
|
||||||
|
transform: translate3d(-1px, 0, 0)
|
||||||
|
20%, 80%
|
||||||
|
transform: translate3d(2px, 0, 0)
|
||||||
|
30%, 50%, 70%
|
||||||
|
transform: translate3d(-4px, 0, 0)
|
||||||
|
40%, 60%
|
||||||
|
transform: translate3d(4px, 0, 0)
|
||||||
|
|
||||||
|
@keyframes fade-in
|
||||||
|
from
|
||||||
|
opacity: 0
|
||||||
|
transform: translateY(-5px)
|
||||||
|
to
|
||||||
|
opacity: 1
|
||||||
|
transform: translateY(0)
|
@ -1,11 +1,40 @@
|
|||||||
import "./styles.sass";
|
import "./styles.sass";
|
||||||
import ActionCard from "@/pages/Home/components/ActionCard";
|
import ActionCard from "@/pages/Home/components/ActionCard";
|
||||||
|
import JoinForm from "@/pages/Home/components/JoinForm";
|
||||||
|
import CreateForm from "@/pages/Home/components/CreateForm";
|
||||||
import {faArrowRightToBracket, faPlusSquare} from "@fortawesome/free-solid-svg-icons";
|
import {faArrowRightToBracket, faPlusSquare} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {StateContext} from "@/common/contexts/StateContext";
|
import {StateContext} from "@/common/contexts/StateContext";
|
||||||
import {useContext} from "react";
|
import {SocketContext} from "@/common/contexts/SocketContext";
|
||||||
|
import {useContext, useState} from "react";
|
||||||
|
|
||||||
export const Home = () => {
|
export const Home = () => {
|
||||||
const {setCurrentState} = useContext(StateContext);
|
const {setCurrentState} = useContext(StateContext);
|
||||||
|
const {connect, send} = useContext(SocketContext);
|
||||||
|
const [homeState, setHomeState] = useState("initial"); // initial, joining, creating
|
||||||
|
|
||||||
|
const handleJoinClick = () => {
|
||||||
|
setHomeState("joining");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateClick = () => {
|
||||||
|
setHomeState("creating");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
setHomeState("initial");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJoinSubmit = (name, roomCode) => {
|
||||||
|
connect();
|
||||||
|
send("joinRoom", {name, roomCode});
|
||||||
|
setCurrentState("Game");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateSubmit = (name) => {
|
||||||
|
connect();
|
||||||
|
send("createRoom", {name});
|
||||||
|
setCurrentState("Game");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="home-page">
|
<div className="home-page">
|
||||||
@ -13,14 +42,28 @@ export const Home = () => {
|
|||||||
<div className="rotating-gradient"></div>
|
<div className="rotating-gradient"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="logo-container">
|
<div className={`logo-container ${homeState !== 'initial' ? 'shifted' : ''}`}>
|
||||||
<h1 className="logo">ToneGuessr</h1>
|
<h1 className="logo">ToneGuessr</h1>
|
||||||
<p className="tagline">Das Frequenzspiel für Marco :3</p>
|
<p className="tagline">Das Frequenzspiel für Marco :3</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="action-area">
|
<div className={`content-container ${homeState !== 'initial' ? 'active' : ''}`}>
|
||||||
<ActionCard title="Join Room" icon={faArrowRightToBracket} onClick={() => {}} />
|
<div className={`action-area ${homeState !== 'initial' ? 'hidden' : ''}`}>
|
||||||
<ActionCard title="Create Room" icon={faPlusSquare} onClick={() => setCurrentState("Game")} />
|
<ActionCard title="Join Room" icon={faArrowRightToBracket} onClick={handleJoinClick} />
|
||||||
|
<ActionCard title="Create Room" icon={faPlusSquare} onClick={handleCreateClick} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`form-container ${homeState === 'joining' ? 'active' : ''}`}>
|
||||||
|
{homeState === 'joining' && (
|
||||||
|
<JoinForm onSubmit={handleJoinSubmit} onBack={handleBack} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`form-container ${homeState === 'creating' ? 'active' : ''}`}>
|
||||||
|
{homeState === 'creating' && (
|
||||||
|
<CreateForm onSubmit={handleCreateSubmit} onBack={handleBack} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
42
client/src/pages/Home/components/CreateForm/CreateForm.jsx
Normal file
42
client/src/pages/Home/components/CreateForm/CreateForm.jsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import "@/common/styles/forms.sass";
|
||||||
|
import {useState} from "react";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faArrowLeft, faPlusSquare} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import FormInput from "@/common/components/FormInput";
|
||||||
|
|
||||||
|
export const CreateForm = ({onSubmit, onBack}) => {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!name.trim()) {
|
||||||
|
setErrors({name: "Gib einen Namen ein"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-base create-form">
|
||||||
|
<button className="back-button" onClick={onBack}>
|
||||||
|
<FontAwesomeIcon icon={faArrowLeft}/>
|
||||||
|
<span>Zurück</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h2>Raum erstellen</h2>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<FormInput placeholder="Benutzername" value={name} onChange={(e) => setName(e.target.value)}
|
||||||
|
error={errors.name}/>
|
||||||
|
|
||||||
|
<button type="submit" className="submit-button create-button">
|
||||||
|
<span>Raum erstellen</span>
|
||||||
|
<FontAwesomeIcon icon={faPlusSquare}/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
1
client/src/pages/Home/components/CreateForm/index.js
Normal file
1
client/src/pages/Home/components/CreateForm/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {CreateForm as default} from "./CreateForm.jsx";
|
51
client/src/pages/Home/components/JoinForm/JoinForm.jsx
Normal file
51
client/src/pages/Home/components/JoinForm/JoinForm.jsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import "@/common/styles/forms.sass";
|
||||||
|
import {useState} from "react";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faArrowLeft, faArrowRightToBracket} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import FormInput from "@/common/components/FormInput";
|
||||||
|
|
||||||
|
export const JoinForm = ({onSubmit, onBack}) => {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [roomCode, setRoomCode] = useState("");
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const newErrors = {};
|
||||||
|
if (!name.trim()) newErrors.name = "Gib einen Namen an";
|
||||||
|
if (!roomCode.trim()) newErrors.roomCode = "Gib einen Raum-Code an";
|
||||||
|
|
||||||
|
if (Object.keys(newErrors).length > 0) {
|
||||||
|
setErrors(newErrors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(name, roomCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form-base join-form">
|
||||||
|
<button className="back-button" onClick={onBack}>
|
||||||
|
<FontAwesomeIcon icon={faArrowLeft}/>
|
||||||
|
<span>Zurück</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h2>Beitreten</h2>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<FormInput placeholder="Benutzername" value={name} onChange={(e) => setName(e.target.value)}
|
||||||
|
error={errors.name}/>
|
||||||
|
|
||||||
|
<FormInput placeholder="Raum-Code" value={roomCode}
|
||||||
|
onChange={(e) => setRoomCode(e.target.value.toUpperCase())} error={errors.roomCode}
|
||||||
|
maxLength={6}/>
|
||||||
|
|
||||||
|
<button type="submit" className="submit-button join-button">
|
||||||
|
<span>Beitreten</span>
|
||||||
|
<FontAwesomeIcon icon={faArrowRightToBracket}/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
2
client/src/pages/Home/components/JoinForm/index.js
Normal file
2
client/src/pages/Home/components/JoinForm/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
export {JoinForm as default} from "./JoinForm.jsx";
|
@ -8,62 +8,69 @@
|
|||||||
height: 100vh
|
height: 100vh
|
||||||
width: 100vw
|
width: 100vw
|
||||||
position: relative
|
position: relative
|
||||||
z-index: 1
|
z-index: 10
|
||||||
animation: page-fade-in 1s ease-in-out
|
|
||||||
|
|
||||||
.logo-container
|
.logo-container
|
||||||
text-align: center
|
text-align: center
|
||||||
margin-bottom: 2rem
|
margin-bottom: 3rem
|
||||||
animation: logo-entrance 1.2s cubic-bezier(0.175, 0.885, 0.32, 1.275)
|
transform: translateY(0)
|
||||||
position: relative
|
transition: transform 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55), margin-bottom 0.6s cubic-bezier(0.68, -0.55, 0.27, 1.55)
|
||||||
|
z-index: 5
|
||||||
.logo
|
|
||||||
font-size: 80pt
|
|
||||||
line-height: 1.1
|
|
||||||
margin: 0
|
|
||||||
padding: 0 15px
|
|
||||||
background: linear-gradient(135deg, $pink, $blue 45%, $mint-green 65%, $yellow 85%, $pink)
|
|
||||||
background-size: 300% 100%
|
|
||||||
animation: logo-shimmer 10s infinite alternate ease-in-out, logo-glow 5s infinite alternate ease-in-out
|
|
||||||
-webkit-background-clip: text
|
|
||||||
background-clip: text
|
|
||||||
-webkit-text-fill-color: transparent
|
|
||||||
filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.6))
|
|
||||||
position: relative
|
|
||||||
letter-spacing: 0.1em
|
|
||||||
font-weight: bold
|
|
||||||
|
|
||||||
&:before
|
&.shifted
|
||||||
content: "ToneGuessr"
|
transform: translateY(-25vh)
|
||||||
position: absolute
|
margin-bottom: 0
|
||||||
z-index: -1
|
|
||||||
left: 0
|
.logo
|
||||||
top: 0
|
font-size: 6rem
|
||||||
background: none
|
margin: 0
|
||||||
-webkit-text-fill-color: transparent
|
color: $white
|
||||||
filter: blur(15px) brightness(1.2)
|
text-shadow: 0 0 20px rgba(255, 255, 255, 0.5)
|
||||||
opacity: 0.7
|
letter-spacing: 0.5rem
|
||||||
width: 100%
|
animation: logo-glow 3s infinite alternate ease-in-out
|
||||||
height: 100%
|
|
||||||
animation: text-pulse 5s infinite alternate ease-in-out
|
.tagline
|
||||||
|
font-size: 1.5rem
|
||||||
.tagline
|
margin-top: 0.5rem
|
||||||
font-size: 24pt
|
opacity: 0.8
|
||||||
margin: 5px 0 0 0
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3)
|
||||||
color: rgba(255, 255, 255, 0.9)
|
font-weight: lighter
|
||||||
text-shadow: 0 0 15px rgba(255, 255, 255, 0.4)
|
letter-spacing: 0.2rem
|
||||||
animation: tagline-fade 2s ease-in-out
|
|
||||||
letter-spacing: 0.1em
|
|
||||||
|
|
||||||
.action-area
|
.content-container
|
||||||
margin-top: 2rem
|
position: relative
|
||||||
display: flex
|
width: 100%
|
||||||
gap: 3rem
|
display: flex
|
||||||
flex-wrap: wrap
|
justify-content: center
|
||||||
justify-content: center
|
align-items: center
|
||||||
position: relative
|
transition: all 0.5s ease
|
||||||
z-index: 2
|
|
||||||
animation: float-up 1.5s ease-out
|
.action-area
|
||||||
|
display: flex
|
||||||
|
gap: 3rem
|
||||||
|
opacity: 1
|
||||||
|
transform: scale(1) translateY(0)
|
||||||
|
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55)
|
||||||
|
z-index: 4
|
||||||
|
|
||||||
|
&.hidden
|
||||||
|
opacity: 0
|
||||||
|
transform: scale(0.8) translateY(30px)
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
|
.form-container
|
||||||
|
position: absolute
|
||||||
|
opacity: 0
|
||||||
|
transform: translateY(30px) scale(0.9)
|
||||||
|
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55)
|
||||||
|
pointer-events: none
|
||||||
|
width: 400px
|
||||||
|
|
||||||
|
&.active
|
||||||
|
opacity: 1
|
||||||
|
transform: translateY(0) scale(1)
|
||||||
|
pointer-events: all
|
||||||
|
z-index: 5
|
||||||
|
|
||||||
@keyframes logo-entrance
|
@keyframes logo-entrance
|
||||||
0%
|
0%
|
||||||
@ -75,11 +82,11 @@
|
|||||||
|
|
||||||
@keyframes logo-glow
|
@keyframes logo-glow
|
||||||
0%, 100%
|
0%, 100%
|
||||||
filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.6))
|
text-shadow: 0 0 20px rgba(255, 255, 255, 0.5)
|
||||||
transform: scale(1) rotate(-1deg)
|
transform: scale(1)
|
||||||
50%
|
50%
|
||||||
filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.9)) drop-shadow(0 0 40px rgba(102, 204, 255, 0.5))
|
text-shadow: 0 0 30px rgba(255, 255, 255, 0.7), 0 0 50px rgba(255, 255, 255, 0.3)
|
||||||
transform: scale(1.05) rotate(1deg)
|
transform: scale(1.02)
|
||||||
|
|
||||||
@keyframes tagline-fade
|
@keyframes tagline-fade
|
||||||
0%
|
0%
|
||||||
|
Loading…
x
Reference in New Issue
Block a user