First Commit

This commit is contained in:
Ralex91
2024-01-31 20:06:10 +01:00
commit e122fe1078
58 changed files with 6853 additions and 0 deletions

BIN
src/assets/2238431_1694.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

6
src/assets/loader.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;display:block;" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="34" stroke="#fff4e4" stroke-width="13" stroke-linecap="square" fill="none">
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform>
<animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s" values="21.362830044410593 192.26547039969535;106.81415022205297 106.81415022205297;21.362830044410593 192.26547039969535" keyTimes="0;0.5;1"></animate>
</circle>
</svg>

After

Width:  |  Height:  |  Size: 704 B

13
src/assets/logo.svg Normal file
View File

@@ -0,0 +1,13 @@
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
xlink="http://www.w3.org/1999/xlink"
xmlns:svgjs="http://svgjs.com/svgjs"
width="100%"
height="100%"
viewBox="10.34000015258789 -73.18000030517578 461.989990234375 77.19000244140625"
>
<g fill="#ff9900">
<path d="M32.84 -26.89L61.68 4.01L69.05 -0.13L51.33 -33.88C61.5 -39.74 66.59 -47.11 66.59 -55.99C66.59 -59.18 65.99 -62.67 64.78 -66.46C59.35 -69.82 52.11 -71.5 43.06 -71.5C37.37 -71.5 31.42 -71.01 25.21 -70.02C19.01 -69.02 14.05 -67.93 10.34 -66.72L16.42 1.42L27.02 0.39L29.87 -26.25C30.64 -26.25 31.64 -26.46 32.84 -26.89ZM46.16 -53.01C46.16 -49.31 44.8 -45.6 42.09 -41.89C39.37 -38.19 35.56 -35.21 30.64 -32.97L33.88 -63.49C35.95 -64.26 37.84 -64.65 39.57 -64.65C43.96 -61.89 46.16 -58.01 46.16 -53.01Z M90.69 0L101.16 1.55L104.78 -13.71L132.97 -16.03L140.34 1.55L149.78 0L136.08 -68.92L110.47 -70.47ZM115.13 -57.15L115.9 -57.15L130.65 -21.59L106.72 -21.85Z M222.63 1.68L233.23 0.65L240.73 -70.21L216.68 -68.66L219.78 -32.2L198.84 -32.33L202.85 -70.47L178.67 -68.92L184.74 1.42L195.35 0.39L197.93 -24.18L220.3 -26.12Z M320.04 -21.98C320.04 -34.14 317.31 -44.87 311.83 -54.18C306.36 -63.49 299.49 -69.82 291.21 -73.18C285.35 -69.22 280.22 -62.62 275.82 -53.4C271.43 -44.18 269.23 -33.7 269.23 -21.98C269.23 -16.21 269.83 -10.34 271.04 -4.4C276.64 0.26 284.51 2.56 294.64 2.52C304.77 2.48 311.6 0.43 315.13 -3.62C316.94 -5.86 318.21 -8.4 318.95 -11.25C319.68 -14.09 320.04 -17.67 320.04 -21.98ZM307.11 -26.64C307.11 -21.12 306.51 -15.6 305.3 -10.09C300.56 -8.53 295.76 -7.76 290.89 -7.76C286.02 -7.76 282.37 -8.02 279.96 -8.53C279.96 -16.46 281.9 -24.55 285.78 -32.78C289.66 -41.01 295 -48.27 301.81 -54.56C305.35 -46.81 307.11 -37.5 307.11 -26.64Z M395.61 -21.98C395.61 -34.14 392.87 -44.87 387.4 -54.18C381.92 -63.49 375.05 -69.82 366.77 -73.18C360.91 -69.22 355.78 -62.62 351.39 -53.4C346.99 -44.18 344.79 -33.7 344.79 -21.98C344.79 -16.21 345.4 -10.34 346.6 -4.4C352.21 0.26 360.07 2.56 370.2 2.52C380.33 2.48 387.16 0.43 390.69 -3.62C392.5 -5.86 393.78 -8.4 394.51 -11.25C395.24 -14.09 395.61 -17.67 395.61 -21.98ZM382.68 -26.64C382.68 -21.12 382.07 -15.6 380.87 -10.09C376.13 -8.53 371.32 -7.76 366.45 -7.76C361.58 -7.76 357.94 -8.02 355.52 -8.53C355.52 -16.46 357.46 -24.55 361.34 -32.78C365.22 -41.01 370.57 -48.27 377.38 -54.56C380.91 -46.81 382.68 -37.5 382.68 -26.64Z M418.29 -69.05L418.93 -58.06L433.67 -59.48L439.1 1.42L450.61 0.39L457.21 -61.81L472.08 -63.36L472.33 -70.47Z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View 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
View 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
View 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
View 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}
/>
)
}

View 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} />
}

View 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>
)
}

View 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>
</>
)
}

View 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>
</>
)
}

View 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>
*/

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

1
src/constants.js Normal file
View File

@@ -0,0 +1 @@
export const WEBSOCKET_URL = "http://localhost:3000" ///"https://rahoot.ralex.app"

51
src/context/player.jsx Normal file
View File

@@ -0,0 +1,51 @@
import { createContext, useContext, useReducer } from "react"
export const PlayerContext = createContext()
export function playerReducer(state, action) {
switch (action.type) {
case "JOIN":
return { player: { ...state.player, room: action.payload } }
case "LOGIN":
return {
player: {
...state.player,
username: action.payload,
points: 0,
},
}
case "UPDATE":
return { player: { ...state.player, ...action.payload } }
case "LOGOUT":
return { player: null }
default:
return state
}
}
export const PlayerContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(playerReducer, {
player: null,
})
return (
<PlayerContext.Provider
value={{
...state,
dispatch,
}}
>
{children}
</PlayerContext.Provider>
)
}
export function usePlayerContext() {
const context = useContext(PlayerContext)
if (!context) {
throw Error("usePlayerContext must be used inside an PlayerContextProvider")
}
return context
}

24
src/context/socket.jsx Normal file
View File

@@ -0,0 +1,24 @@
import { io } from "socket.io-client"
import { createContext, useContext, useState } from "react"
import { WEBSOCKET_URL } from "@/constants"
export const socket = io("http://localhost:5057", {
path: "/ws/",
//addTrailingSlash: false,
transports: ["websocket"],
})
export const SocketContext = createContext()
export const SocketContextProvider = ({ children }) => {
return (
<SocketContext.Provider value={socket}>{children}</SocketContext.Provider>
)
}
export function useSocketContext() {
const context = useContext(SocketContext)
const [isConnected, setIsConnected] = useState(false)
return { socket: context }
}

24
src/pages/_app.js Normal file
View File

@@ -0,0 +1,24 @@
import { PlayerContextProvider } from "@/context/player"
import { SocketContextProvider } from "@/context/socket"
import "@/styles/globals.css"
import clsx from "clsx"
import { Montserrat, Plaster } from "next/font/google"
const montserrat = Montserrat({ subsets: ["latin"] })
export default function App({ Component, pageProps }) {
return (
<SocketContextProvider>
<PlayerContextProvider>
<main
className={clsx(
"flex flex-col text-base-[8px]",
montserrat.className
)}
>
<Component {...pageProps} />
</main>
</PlayerContextProvider>
</SocketContextProvider>
)
}

13
src/pages/_document.js Normal file
View File

@@ -0,0 +1,13 @@
import { Html, Head, Main, NextScript } from "next/document"
export default function Document() {
return (
<Html lang="en">
<Head />
<body className="bg-secondary">
<Main />
<NextScript />
</body>
</Html>
)
}

5
src/pages/api/hello.js Normal file
View File

@@ -0,0 +1,5 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: "John Doe" });
}

27
src/pages/api/socket.js Normal file
View File

@@ -0,0 +1,27 @@
import { Server } from "socket.io"
const socketHandler = (req, res) => {
if (!res.socket.server.io) {
const path = "/api/socket"
const httpServer = res.socket.server
//const io = createIOServer(httpServer, path)
const io = new Server(httpServer, {
path: path,
addTrailingSlash: false,
})
res.socket.server.io = io
console.log("SocketIO server started at " + path)
}
res.end()
}
export const config = {
api: {
bodyParser: false,
},
}
export default socketHandler

64
src/pages/game.jsx Normal file
View File

@@ -0,0 +1,64 @@
import GameWrapper from "@/components/game/GameWrapper"
import Answers from "@/components/game/states/Answers"
import Leaderboard from "@/components/game/states/Leaderboard"
import Question from "@/components/game/states/Question"
import Result from "@/components/game/states/Result"
import Wait from "@/components/game/states/Wait"
import { usePlayerContext } from "@/context/player"
import { useSocketContext } from "@/context/socket"
import { useRouter } from "next/router"
import { createElement, useState } from "react"
const gameStateComponent = {
SELECT_ANSWER: Answers,
SHOW_QUESTION: Question,
WAIT: Wait,
SHOW_RESULT: Result,
}
export default function Game() {
const router = useRouter()
const { socket } = useSocketContext()
const { player } = usePlayerContext()
if (!player) {
//router.push("/")
return
}
socket.emit("player:join", { username: "Test", room: player.room })
const [state, setState] = useState({
status: {
name: "WAIT",
data: { text: "Waiting for the players" },
},
question: {
current: 1,
total: null,
},
})
socket.on("game:status", (status) => {
setState({
...gameState,
status: status,
})
})
/*socket.on("game:", (question) => {
setState({
...state,
})*/
return (
<GameWrapper>
{gameStateComponent[state.status.name] &&
createElement(gameStateComponent[state.status.name], {
data: state.status.data,
})}
</GameWrapper>
)
}

36
src/pages/index.js Normal file
View File

@@ -0,0 +1,36 @@
import Image from "next/image"
import { Montserrat } from "next/font/google"
import Form from "@/components/Form"
import Button from "@/components/Button"
import Input from "@/components/Input"
import logo from "@/assets/logo.svg"
import { useState } from "react"
import Loader from "@/components/Loader"
import { usePlayerContext } from "@/context/player"
import Room from "@/components/game/join/Room"
import Username from "@/components/game/join/Username"
export default function Home() {
const [loading, setLoading] = useState(false)
const { player } = usePlayerContext()
return (
<section className="min-h-screen relative flex justify-center items-center flex-col">
<div className="absolute w-full h-full overflow-hidden">
<div className="absolute min-w-[75vmin] min-h-[75vmin] -top-[15vmin] -left-[15vmin] bg-primary/15 rounded-full"></div>
<div className="absolute min-w-[75vmin] min-h-[75vmin] -bottom-[15vmin] -right-[15vmin] bg-primary/15 rotate-45"></div>
</div>
{loading && (
<div className="absolute w-full h-full z-30 bg-black/40 flex justify-center items-center">
<Loader />
</div>
)}
<Image src={logo} className="h-10 mb-6" />
{!player ? <Room /> : <Username />}
</section>
)
}

63
src/pages/manager.jsx Normal file
View File

@@ -0,0 +1,63 @@
import GameWrapper from "@/components/game/GameWrapper"
import Answers from "@/components/game/states/Answers"
import Leaderboard from "@/components/game/states/Leaderboard"
import Question from "@/components/game/states/Question"
import Wait from "@/components/game/states/Wait"
import { useSocketContext } from "@/context/socket"
import { createElement, useEffect, useMemo, useState } from "react"
export default function Manager({ children }) {
const { socket } = useSocketContext()
const [gameState, setGameState] = useState({
status: {
name: "PENDING",
},
cooldown: 0,
answers: [],
})
socket.on("game:status", (status) => {
setGameState({
...gameState,
status: status,
cooldown: 0,
})
})
return (
<>
<p className="text-white">{JSON.stringify(gameState)}</p>
<hr></hr>
<div className="flex gap-2">
<button
className="bg-primary p-2"
onClick={() => socket.emit("manager:createRoom")}
>
Create Room
</button>
<button
className="bg-primary p-2"
onClick={() => socket.emit("manager:startGame")}
>
Start Game
</button>
<button
className="bg-primary p-2"
onClick={() => socket.emit("manager:showLeaderboard")}
>
Leaderboard
</button>
<button
className="bg-primary p-2"
onClick={() => socket.emit("manager:nextQuestion")}
>
Next Question
</button>
</div>
</>
)
}

43
src/pages/question.jsx Normal file
View File

@@ -0,0 +1,43 @@
import Image from "next/image"
import Form from "@/components/Form"
import Button from "@/components/Button"
import Input from "@/components/Input"
import logo from "@/assets/logo.svg"
import { useEffect, useRef, useState } from "react"
import Loader from "@/components/Loader"
import { useSocketContext } from "@/context/socket"
import background from "@/assets/2238431_1694.jpg"
export default function Questionasas() {
const [loading, setLoading] = useState(false)
const socket = useSocketContext()
const barRef = useRef(null)
useEffect(() => {
setTimeout(() => {
barRef.current.style.width = "100%"
})
}, [])
return (
<section className="min-h-screen relative flex justify-center items-center flex-col max-w-7xl mx-auto">
<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>
<h2 className="text-white text-5xl font-bold drop-shadow-lg text-center">
Lorem ipsum dolor sit ametorrupti. Alias, recusandae officia.
</h2>
<div
ref={barRef}
className="absolute h-6 bg-primary bottom-0 mb-32 rounded-full w-0 self-start"
style={{ transition: "width 5s linear" }}
></div>
</section>
)
}

52
src/styles/globals.css Normal file
View File

@@ -0,0 +1,52 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.btn-shadow {
box-shadow: rgba(0, 0, 0, 0.25) 0px -4px inset;
}
.btn-shadow span {
display: block;
transform: translateY(-2px);
}
.btn-shadow:hover {
box-shadow: rgba(0, 0, 0, 0.25) 0px -2px inset;
}
.btn-shadow:hover span {
transform: translateY(0);
}
.btn-shadow:active {
transform: translateY(1px);
box-shadow: none;
}
.text-outline {
-webkit-text-stroke: 2px rgba(0, 0, 0, 0.25);
}
.shadow-inset {
box-shadow: rgba(0, 0, 0, 0.25) 0px -4px inset;
}
.anim-show {
animation: show 0.5s ease-out;
}
@keyframes show {
0% {
transform: scale(0);
}
30% {
transform: scale(0.9);
}
60% {
transform: scale(0.8);
}
80% {
transform: scale(1);
}
}

0
src/validator.js Normal file
View File