mirror of
https://github.com/randyjc/Rahoot.git
synced 2026-03-14 04:25:35 +01:00
First Commit
This commit is contained in:
22
src/components/AnswerButton.jsx
Normal file
22
src/components/AnswerButton.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import clsx from "clsx"
|
||||
import Triangle from "./icons/Triangle"
|
||||
|
||||
export default function AnswerButton({
|
||||
className,
|
||||
icon: Icon,
|
||||
children,
|
||||
...otherProps
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
"text-left px-4 py-6 rounded shadow-inset flex items-center gap-3",
|
||||
className
|
||||
)}
|
||||
{...otherProps}
|
||||
>
|
||||
<Icon className="h-6 w-6" />
|
||||
<span className="drop-shadow-md">{children}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
15
src/components/Button.jsx
Normal file
15
src/components/Button.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
export default function Button({ children, className, ...otherProps }) {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
"p-2 bg-primary rounded-md text-white font-semibold text-lg btn-shadow",
|
||||
className
|
||||
)}
|
||||
{...otherProps}
|
||||
>
|
||||
<span>{children}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
7
src/components/Form.jsx
Normal file
7
src/components/Form.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Form({ children }) {
|
||||
return (
|
||||
<div className="bg-white rounded-md shadow-sm p-4 z-10 flex-col flex gap-4 w-full max-w-80">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
13
src/components/Input.jsx
Normal file
13
src/components/Input.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
export default function Input({ className, ...otherProps }) {
|
||||
return (
|
||||
<input
|
||||
className={clsx(
|
||||
"outline outline-gray-300 outline-2 rounded-sm p-2 font-semibold text-lg",
|
||||
className
|
||||
)}
|
||||
{...otherProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
6
src/components/Loader.jsx
Normal file
6
src/components/Loader.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import loader from "@/assets/loader.svg"
|
||||
import Image from "next/image"
|
||||
|
||||
export default function Loader({ ...otherProps }) {
|
||||
return <Image src={loader} />
|
||||
}
|
||||
35
src/components/game/GameWrapper.jsx
Normal file
35
src/components/game/GameWrapper.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import Image from "next/image"
|
||||
import Button from "@/components/Button"
|
||||
import background from "@/assets/2238431_1694.jpg"
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
|
||||
export default function GameWrapper({ children }) {
|
||||
const { player } = usePlayerContext()
|
||||
|
||||
return (
|
||||
<section className="relative flex justify-between flex-col w-full min-h-screen">
|
||||
<div className="fixed h-full w-full top-0 left-0 bg-orange-600 opacity-70 -z-10">
|
||||
<Image
|
||||
className="object-cover h-full w-full opacity-60 pointer-events-none"
|
||||
src={background}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-4 w-full flex justify-between">
|
||||
<div className="bg-white shadow-inset text-black px-4 font-bold rounded-md flex items-center text-lg">
|
||||
1/10
|
||||
</div>
|
||||
<Button className="bg-white !text-black px-4">Skip</Button>
|
||||
</div>
|
||||
|
||||
{children}
|
||||
|
||||
<div className="bg-white py-2 px-4 flex items-center text-lg justify-between font-bold text-white">
|
||||
<p className="text-gray-800">{!!player && player.username}</p>
|
||||
<div className="bg-gray-800 rounded-sm py-1 px-3 text-lg">
|
||||
{!!player && player.points}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
33
src/components/game/join/Room.jsx
Normal file
33
src/components/game/join/Room.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
import Form from "@/components/Form"
|
||||
import Button from "@/components/Button"
|
||||
import Input from "@/components/Input"
|
||||
import { useState } from "react"
|
||||
|
||||
export default function Room() {
|
||||
const { player, dispatch } = usePlayerContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [roomId, setRoomId] = useState("")
|
||||
|
||||
const handleLogin = () => {
|
||||
dispatch({ type: "JOIN", payload: roomId })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && (
|
||||
<div className="absolute w-full h-full z-30 bg-black/40 flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form>
|
||||
<Input
|
||||
onChange={(e) => setRoomId(e.target.value)}
|
||||
placeholder="PIN Code here"
|
||||
/>
|
||||
<Button onClick={() => handleLogin()}>Submit</Button>
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
42
src/components/game/join/Username.jsx
Normal file
42
src/components/game/join/Username.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
import Form from "@/components/Form"
|
||||
import Button from "@/components/Button"
|
||||
import Input from "@/components/Input"
|
||||
import { useState } from "react"
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import { useRouter } from "next/router"
|
||||
|
||||
export default function Username() {
|
||||
const { socket } = useSocketContext()
|
||||
const { player, dispatch } = usePlayerContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
const [username, setUsername] = useState("")
|
||||
|
||||
const handleJoin = () => {
|
||||
dispatch({
|
||||
type: "LOGIN",
|
||||
payload: username,
|
||||
})
|
||||
|
||||
router.push("/game")
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && (
|
||||
<div className="absolute w-full h-full z-30 bg-black/40 flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form>
|
||||
<Input
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="Usernname here"
|
||||
/>
|
||||
<Button onClick={() => handleJoin()}>Submit</Button>
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
76
src/components/game/states/Answers.jsx
Normal file
76
src/components/game/states/Answers.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import Circle from "@/components/icons/Circle"
|
||||
import Triangle from "@/components/icons/Triangle"
|
||||
import Square from "@/components/icons/Square"
|
||||
import Rhombus from "@/components/icons/Rhombus"
|
||||
import AnswerButton from "../../AnswerButton"
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import Image from "next/image"
|
||||
import { useState } from "react"
|
||||
|
||||
const answersColors = [
|
||||
"bg-red-500",
|
||||
"bg-blue-500",
|
||||
"bg-yellow-500",
|
||||
"bg-green-500",
|
||||
]
|
||||
|
||||
const answersIcons = [Triangle, Rhombus, Circle, Square]
|
||||
|
||||
export default function Answers({ data: { question, answers, image, time } }) {
|
||||
const { socket } = useSocketContext()
|
||||
|
||||
const [cooldown, setCooldown] = useState(time)
|
||||
const [totalAnswer, setTotalAnswer] = useState(0)
|
||||
|
||||
socket.on("game:cooldown", (sec) => {
|
||||
setCooldown(sec)
|
||||
})
|
||||
|
||||
socket.on("game:playerAnswer", (count) => {
|
||||
setTotalAnswer(count)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col justify-between flex-1">
|
||||
<div className="h-full max-w-7xl mx-auto w-full inline-flex flex-col items-center justify-center gap-5 flex-1">
|
||||
<h2 className="text-white text-2xl md:text-4xl font-bold drop-shadow-lg text-center">
|
||||
{question}
|
||||
</h2>
|
||||
|
||||
{/*<Image src={image} className="h-60 w-auto rounded-md" />*/}
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<div className="max-w-7xl mx-auto mb-4 rounded-full w-full flex justify-between gap-1 font-bold text-white text-lg md:text-xl px-2">
|
||||
<div className="bg-white shadow-inset text-black px-4 font-bold rounded-md flex flex-col items-center text-lg">
|
||||
<span className="text-sm">Time</span>
|
||||
<span className="-translate-y-1">{cooldown}</span>
|
||||
</div>
|
||||
<div className="bg-white shadow-inset text-black px-4 font-bold rounded-md flex flex-col items-center text-lg">
|
||||
<span className="text-sm">Answers</span>
|
||||
<span className="-translate-y-1">{totalAnswer}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto mb-4 rounded-full w-full grid grid-cols-2 gap-1 font-bold text-white text-lg md:text-xl px-2">
|
||||
{answers.map((answer, key) => (
|
||||
<AnswerButton
|
||||
key={key}
|
||||
className={answersColors[key]}
|
||||
icon={answersIcons[key]}
|
||||
onClick={() => socket.emit("player:selectedAnswer", key)}
|
||||
>
|
||||
{answer}
|
||||
</AnswerButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* OLD Timer
|
||||
<div className="absolute left-8 -translate-y-1/2 top-2/4 text-white font-bold text-6xl rounded-full justify-center items-center bg-orange-400 p-8 aspect-square hidden 2xl:flex">
|
||||
<span </div>className="drop-shadow-md">20</span>
|
||||
</div>
|
||||
*/
|
||||
25
src/components/game/states/Leaderboard.jsx
Normal file
25
src/components/game/states/Leaderboard.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
export default function Leaderboard({ data }) {
|
||||
return (
|
||||
<section className="max-w-7xl mx-auto w-full flex-1 flex-col relative items-center justify-center flex">
|
||||
<h2 className="text-white drop-shadow-md text-5xl font-bold mb-6">
|
||||
Leaderboard
|
||||
</h2>
|
||||
<div className="w-full flex-col flex gap-2">
|
||||
<div className=" bg-orange-500 rounded-md p-3 flex justify-between w-full text-white font-bold text-2xl">
|
||||
<span>Username</span>
|
||||
<span>10000</span>
|
||||
</div>
|
||||
|
||||
<div className="bg-orange-500 rounded-md p-3 flex justify-between w-full text-white font-bold text-2xl">
|
||||
<span>Username</span>
|
||||
<span>10000</span>
|
||||
</div>
|
||||
|
||||
<div className="bg-orange-500 rounded-md p-3 flex justify-between w-full text-white font-bold text-2xl">
|
||||
<span>Username</span>
|
||||
<span>10000</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
27
src/components/game/states/Question.jsx
Normal file
27
src/components/game/states/Question.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useSocketContext } from "@/context/socket"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
export default function Question({ data: { question } }) {
|
||||
const barRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
barRef.current.style.width = "100%"
|
||||
}, 1)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="max-w-7xl mx-auto w-full flex-1 relative items-center flex flex-col px-4 h-full">
|
||||
<div className="flex items-center flex-1">
|
||||
<h2 className="text-white text-5xl font-bold drop-shadow-lg text-center anim-show w-full justify-self-center">
|
||||
{question}
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
ref={barRef}
|
||||
className="h-6 bg-primary mb-32 rounded-full w-0 self-start justify-self-end"
|
||||
style={{ transition: `width ${6}s linear` }}
|
||||
></div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
35
src/components/game/states/Result.jsx
Normal file
35
src/components/game/states/Result.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import CricleCheck from "@/components/icons/CricleCheck"
|
||||
import CricleXmark from "@/components/icons/CricleXmark"
|
||||
import { usePlayerContext } from "@/context/player"
|
||||
import { useEffect } from "react"
|
||||
|
||||
export default function Result({
|
||||
data: { correct, message, points, myPoints, totalPlayer, rank },
|
||||
}) {
|
||||
const { dispatch } = usePlayerContext()
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: "UPDATE",
|
||||
payload: { points: myPoints },
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="max-w-7xl mx-auto w-full flex-1 relative items-center justify-center flex flex-col">
|
||||
{correct ? (
|
||||
<CricleCheck className="max-h-60 aspect-square w-full" />
|
||||
) : (
|
||||
<CricleXmark className=" max-h-60 aspect-square w-full" />
|
||||
)}
|
||||
<h2 className="text-white font-bold text-4xl mt-1 drop-shadow-lg">
|
||||
{message}
|
||||
</h2>
|
||||
{correct && (
|
||||
<span className="py-2 px-4 text-white font-bold text-2xl drop-shadow-lg bg-black/40 rounded mt-2">
|
||||
+{points}
|
||||
</span>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
12
src/components/game/states/Wait.jsx
Normal file
12
src/components/game/states/Wait.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import Loader from "@/components/Loader"
|
||||
|
||||
export default function Wait({ data: { text } }) {
|
||||
return (
|
||||
<section className="max-w-7xl mx-auto w-full flex-1 relative items-center justify-center flex flex-col">
|
||||
<Loader />
|
||||
<h2 className="text-white font-bold text-4xl mt-5 drop-shadow-lg">
|
||||
{text}
|
||||
</h2>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
25
src/components/icons/Circle.jsx
Normal file
25
src/components/icons/Circle.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
export default function Circle({ className, fill = "#FFF" }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
id="Page-1"
|
||||
stroke="none"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
fillRule="evenodd"
|
||||
>
|
||||
<g id="icon" fill={fill} transform="translate(42.666667, 42.666667)">
|
||||
<path
|
||||
d="M213.333333,3.55271368e-14 C331.15408,3.55271368e-14 426.666667,95.5125867 426.666667,213.333333 C426.666667,331.15408 331.15408,426.666667 213.333333,426.666667 C95.5125867,426.666667 3.55271368e-14,331.15408 3.55271368e-14,213.333333 C3.55271368e-14,95.5125867 95.5125867,3.55271368e-14 213.333333,3.55271368e-14 Z"
|
||||
id="Combined-Shape"
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
37
src/components/icons/CricleCheck.jsx
Normal file
37
src/components/icons/CricleCheck.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
export default function CricleCheck({ className }) {
|
||||
return (
|
||||
<svg
|
||||
fill="#28d524"
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 56.00 56.00"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="#28d524"
|
||||
strokeWidth="0.00056"
|
||||
className={className}
|
||||
>
|
||||
<g strokeWidth="0" transform="translate(11.2,11.2), scale(0.6)">
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="56.00"
|
||||
height="56.00"
|
||||
rx="28"
|
||||
fill="#ffffff"
|
||||
strokewidth="0"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
stroke="#CCCCCC"
|
||||
strokeWidth="0.6719999999999999"
|
||||
/>
|
||||
|
||||
<g>
|
||||
<path d="M 27.9999 51.9063 C 41.0546 51.9063 51.9063 41.0781 51.9063 28 C 51.9063 14.9453 41.0312 4.0937 27.9765 4.0937 C 14.8983 4.0937 4.0937 14.9453 4.0937 28 C 4.0937 41.0781 14.9218 51.9063 27.9999 51.9063 Z M 24.7655 40.0234 C 23.9687 40.0234 23.3593 39.6719 22.6796 38.8750 L 15.9296 30.5312 C 15.5780 30.0859 15.3671 29.5234 15.3671 29.0078 C 15.3671 27.9063 16.2343 27.0625 17.2655 27.0625 C 17.9452 27.0625 18.5077 27.3203 19.0702 28.0469 L 24.6718 35.2890 L 35.5702 17.8281 C 36.0155 17.1016 36.6249 16.75 37.2343 16.75 C 38.2655 16.75 39.2733 17.4297 39.2733 18.5547 C 39.2733 19.0703 38.9687 19.6328 38.6640 20.1016 L 26.7577 38.8750 C 26.2421 39.6484 25.5858 40.0234 24.7655 40.0234 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
31
src/components/icons/CricleXmark.jsx
Normal file
31
src/components/icons/CricleXmark.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
export default function CricleXmark({ className }) {
|
||||
return (
|
||||
<svg
|
||||
fill="#fb1e1e"
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 56.00 56.00"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="#fb1e1e"
|
||||
className={className}
|
||||
>
|
||||
<g strokeWidth="0" transform="translate(12.4,12.4), scale(0.6)">
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="56.00"
|
||||
height="56.00"
|
||||
rx="28"
|
||||
fill="#ffffff"
|
||||
strokewidth="0"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g strokeLinecap="round" strokeLinejoin="round" />
|
||||
|
||||
<g>
|
||||
<path d="M 27.9999 51.9063 C 41.0546 51.9063 51.9063 41.0781 51.9063 28 C 51.9063 14.9453 41.0312 4.0937 27.9765 4.0937 C 14.8983 4.0937 4.0937 14.9453 4.0937 28 C 4.0937 41.0781 14.9218 51.9063 27.9999 51.9063 Z M 19.5858 38.4063 C 18.4843 38.4063 17.5936 37.5156 17.5936 36.4141 C 17.5936 35.8750 17.8280 35.4063 18.2030 35.0547 L 25.1874 28.0234 L 18.2030 20.9922 C 17.8280 20.6641 17.5936 20.1719 17.5936 19.6328 C 17.5936 18.5547 18.4843 17.6875 19.5858 17.6875 C 20.1249 17.6875 20.5936 17.8984 20.9452 18.2734 L 27.9765 25.2812 L 35.0546 18.25 C 35.4530 17.8281 35.8749 17.6406 36.3905 17.6406 C 37.4921 17.6406 38.3827 18.5312 38.3827 19.6094 C 38.3827 20.1484 38.1952 20.5937 37.7968 20.9688 L 30.7655 28.0234 L 37.7733 35.0078 C 38.1249 35.3828 38.3593 35.8516 38.3593 36.4141 C 38.3593 37.5156 37.4687 38.4063 36.3671 38.4063 C 35.8046 38.4063 35.3358 38.1719 34.9843 37.8203 L 27.9765 30.7890 L 20.9921 37.8203 C 20.6405 38.1953 20.1249 38.4063 19.5858 38.4063 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
19
src/components/icons/Rhombus.jsx
Normal file
19
src/components/icons/Rhombus.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
export default function Rhombus({ className, fill = "#FFF" }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
fill={fill}
|
||||
viewBox="-56.32 -56.32 624.64 624.64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
transform="rotate(45)"
|
||||
>
|
||||
<g strokeWidth="0" />
|
||||
|
||||
<g strokeLinecap="round" strokeLinejoin="round" />
|
||||
|
||||
<g>
|
||||
<rect x="48" y="48" width="416" height="416" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
12
src/components/icons/Square.jsx
Normal file
12
src/components/icons/Square.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function Square({ className, fill = "#FFF" }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
fill={fill}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="48" y="48" width="416" height="416" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
12
src/components/icons/Triangle.jsx
Normal file
12
src/components/icons/Triangle.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function Triangle({ className, fill = "#FFF" }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
fill={fill}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polygon points="256 32 20 464 492 464 256 32" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user