First commit
This commit is contained in:
commit
48866cc6ab
20 changed files with 6301 additions and 0 deletions
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
0
Dockerfile
Normal file
0
Dockerfile
Normal file
36
README.md
Normal file
36
README.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
7
app/[id]/forceUpdate.tsx
Normal file
7
app/[id]/forceUpdate.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
'use client'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function UseForceUpdate() {
|
||||
const [value, setValue] = useState(0); // integer state
|
||||
return () => setValue(value => value + 1); // update state to force render
|
||||
}
|
274
app/[id]/page.tsx
Normal file
274
app/[id]/page.tsx
Normal file
|
@ -0,0 +1,274 @@
|
|||
'use client'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { io } from "socket.io-client"
|
||||
import { getRandomQuestion } from "./questions"
|
||||
import { useForceUpdate } from "./forceUpdate"
|
||||
import { IconCrown } from "@tabler/icons-react"
|
||||
|
||||
interface roomProps {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Home({ params }: roomProps) {
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
const [forceUpdate, setForceUpdate] = useState(0)
|
||||
|
||||
const [role, setRole] = useState("")
|
||||
const [name, setName] = useState("")
|
||||
|
||||
const [gameStarted, setGameStarted] = useState(false)
|
||||
const [gameEnded, setGameEnded] = useState(false)
|
||||
const [questionDisplayed, setQuestionDisplayed] = useState("")
|
||||
const [possibleChoice, setPossibleChoice] = useState([])
|
||||
const [totalVotes, setTotalVotes] = useState(0)
|
||||
const [choice, setChoice] = useState("")
|
||||
const [countdown, setCountdown] = useState(0)
|
||||
const [players, setPlayers] = useState([])
|
||||
const [questionNbr, setQuestionNbr] = useState(0)
|
||||
const [questionAlreadyDone, setQuestionAlreadyDone] = useState([])
|
||||
|
||||
const socketRef = useRef()
|
||||
const duration = 15
|
||||
const questionLimit = 10
|
||||
|
||||
const { id } = params
|
||||
const roomNameDisplay = id.substring(0,3) + " " + id.substring(3,6)
|
||||
|
||||
useEffect(() => {
|
||||
const username = localStorage.getItem('name')
|
||||
setName(username)
|
||||
// Listen for incoming setMessages
|
||||
socketRef.current = io("ws://localhost:3000");
|
||||
|
||||
socketRef.current.on("connect", () => {
|
||||
setIsConnected(true)
|
||||
socketRef.current.emit('room_connect', {"id": id, "name": username})
|
||||
});
|
||||
|
||||
socketRef.current.on("new_player", (params) => {
|
||||
setPlayers(oldPlayers => [...oldPlayers, params])
|
||||
})
|
||||
|
||||
socketRef.current.on("start_game", (params) => {
|
||||
setQuestionNbr(oldQ => oldQ + 1)
|
||||
setGameStarted(true)
|
||||
setQuestionDisplayed(params.question)
|
||||
setPossibleChoice(params.possibleChoice)
|
||||
setCountdown(params.duration)
|
||||
})
|
||||
|
||||
socketRef.current.on("next_question", (params) => {
|
||||
setQuestionNbr(oldQ => oldQ + 1)
|
||||
setChoice("")
|
||||
setQuestionDisplayed(params.question)
|
||||
setPossibleChoice(params.possibleChoice)
|
||||
setCountdown(params.duration)
|
||||
setTotalVotes(0)
|
||||
})
|
||||
|
||||
socketRef.current.on("reset_game", (params) => {
|
||||
setGameStarted(false)
|
||||
setCountdown(0)
|
||||
setPossibleChoice([])
|
||||
setQuestionDisplayed("")
|
||||
setChoice("")
|
||||
setQuestionNbr(0)
|
||||
setTotalVotes(0)
|
||||
})
|
||||
|
||||
socketRef.current.on("room_joined", (params) => {
|
||||
setPlayers(params.room_users)
|
||||
setRole(params.role)
|
||||
})
|
||||
|
||||
// Clean up the socket connection on unmount
|
||||
return () => {
|
||||
socketRef.current.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
function forceUpdateFunc(){
|
||||
setForceUpdate(fu => fu + 1)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const eventListener = (params) => {
|
||||
setTotalVotes(oldTotal => oldTotal += 1)
|
||||
console.log(params)
|
||||
let temp_possibleChoice = possibleChoice
|
||||
temp_possibleChoice.forEach((playerChoice) => {
|
||||
if(playerChoice.name == params.choice) {
|
||||
playerChoice.nbrVotes += 1
|
||||
}
|
||||
})
|
||||
forceUpdateFunc()
|
||||
};
|
||||
socketRef.current.on("player_choice", eventListener)
|
||||
return () => socketRef.current.off("player_choice", eventListener)
|
||||
}, [possibleChoice])
|
||||
|
||||
useEffect(() => {
|
||||
countdown > 0 && setTimeout(() => setCountdown(countdown - 1), 1000);
|
||||
}, [countdown]);
|
||||
|
||||
function getPossibleChoice() {
|
||||
let choice1 = Math.floor(Math.random() * players.length)
|
||||
console.log(players.length)
|
||||
let choice2 = Math.floor(Math.random() * players.length)
|
||||
console.log(choice2)
|
||||
while(choice2 == choice1){
|
||||
choice2 = Math.floor(Math.random() * players.length)
|
||||
}
|
||||
let playerChoice1 = players[choice1]
|
||||
playerChoice1.nbrVotes = 0
|
||||
let playerChoice2 = players[choice2]
|
||||
playerChoice2.nbrVotes = 0
|
||||
return [playerChoice1, playerChoice2]
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
let possibleChoice = getPossibleChoice()
|
||||
let questionObj = getRandomQuestion()
|
||||
let question = questionObj.question
|
||||
setQuestionAlreadyDone(questionObj.alreadyDone)
|
||||
socketRef.current.emit("start_game", {roomId: id, question: question, possibleChoice: possibleChoice, duration: duration})
|
||||
setCountdown(duration)
|
||||
setPossibleChoice(possibleChoice)
|
||||
setQuestionDisplayed(question)
|
||||
setGameStarted(true)
|
||||
setQuestionNbr(oldQ => oldQ + 1)
|
||||
}
|
||||
|
||||
function nextQuestion() {
|
||||
let possibleChoice = getPossibleChoice()
|
||||
let questionObj = getRandomQuestion(questionAlreadyDone)
|
||||
let question = questionObj.question
|
||||
setQuestionAlreadyDone(questionObj.alreadyDone)
|
||||
socketRef.current.emit("next_question", {roomId: id, question: question, possibleChoice: possibleChoice, duration: duration})
|
||||
setCountdown(duration)
|
||||
setPossibleChoice(possibleChoice)
|
||||
setQuestionDisplayed(question)
|
||||
setChoice("")
|
||||
setQuestionNbr(oldQ => oldQ + 1)
|
||||
setTotalVotes(0)
|
||||
}
|
||||
|
||||
function resetGame() {
|
||||
socketRef.current.emit("reset_game", {roomId: id})
|
||||
setGameStarted(false)
|
||||
setCountdown(0)
|
||||
setPossibleChoice([])
|
||||
setQuestionDisplayed("")
|
||||
setChoice("")
|
||||
setQuestionNbr(0)
|
||||
setTotalVotes(0)
|
||||
}
|
||||
|
||||
function setAndSendChoice(playerName) {
|
||||
setChoice(playerName)
|
||||
setTotalVotes(oldTotal => oldTotal += 1)
|
||||
let temp_possibleChoice = possibleChoice
|
||||
temp_possibleChoice.forEach((playerChoice) => {
|
||||
if(playerChoice.name == playerName) {
|
||||
playerChoice.nbrVotes += 1
|
||||
}
|
||||
})
|
||||
setPossibleChoice(temp_possibleChoice)
|
||||
socketRef.current.emit("player_choice", {roomId: id, choice: playerName, player: name})
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center space-y-16 p-4">
|
||||
{ !gameStarted &&
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-3xl">{roomNameDisplay}</h1>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
{ !isConnected &&
|
||||
<p>Connecting to room...</p>
|
||||
}
|
||||
{ (isConnected && players.length == 0) &&
|
||||
<p>Waiting for players to join...</p>
|
||||
}
|
||||
{ (isConnected && players.length > 0) &&
|
||||
<div className="flex flex-col space-y-16">
|
||||
<div>
|
||||
{ players.map((player) => {
|
||||
console.log(player)
|
||||
if(player.role == "player") {
|
||||
return (
|
||||
<p>{player.name}</p>
|
||||
)
|
||||
}
|
||||
if(player.role == "owner") {
|
||||
return (
|
||||
<p className="flex flex-row">{player.name}<IconCrown /></p>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
{ role == "owner" &&
|
||||
<button className="btn btn-primary" onClick={startGame}>Start game</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{ gameStarted &&
|
||||
<>
|
||||
<div className="flex flex-col space-y-4 items-center">
|
||||
<h1 className="text-3xl">{questionDisplayed}</h1>
|
||||
<span className="indicator-item badge indicator-bottom indicator-center opacity-30">{questionNbr}/{questionLimit}</span>
|
||||
</div>
|
||||
{ countdown > 0 &&
|
||||
<>
|
||||
<span className="countdown">
|
||||
<span style={{"--value":countdown.toString()}}></span>
|
||||
</span>
|
||||
<div className="flex flex-row space-x-4">
|
||||
{ choice == "" &&
|
||||
<>
|
||||
{ possibleChoice.map((playerChoice) => (
|
||||
<button className="btn btn-primary" disabled={choice != ""} onClick={() => {setAndSendChoice(playerChoice.name)}}>{playerChoice.name}</button>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
{ choice != "" &&
|
||||
<span>Waiting <span className="loading loading-dots loading-xs"></span></span>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{ countdown == 0 &&
|
||||
<>
|
||||
<div className="flex flex-row space-x-4">
|
||||
{ possibleChoice.map((playerChoice) => {
|
||||
console.log(totalVotes)
|
||||
console.log(playerChoice.nbrVotes / totalVotes)
|
||||
let percent = Math.floor((playerChoice.nbrVotes / totalVotes) * 100)
|
||||
return (
|
||||
<div className="flex items-center space-y-4 flex-col">
|
||||
<div className="radial-progress text-primary" style={{ "--value": percent }} role="progressbar">
|
||||
{percent}%
|
||||
</div>
|
||||
<h1>{playerChoice.name}</h1>
|
||||
</div>
|
||||
)})}
|
||||
</div>
|
||||
{ (role == "owner" && questionNbr < questionLimit) &&
|
||||
<button className="btn btn-primary" onClick={nextQuestion}>Next question</button>
|
||||
}
|
||||
{ (role == "owner" && questionNbr == questionLimit) &&
|
||||
<button className="btn btn-primary" onClick={resetGame}>Replay</button>
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
</main>
|
||||
);
|
||||
}
|
107
app/[id]/questions.ts
Normal file
107
app/[id]/questions.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
export const questions = [
|
||||
//"Qui organiserait le mieux des vacances ?",
|
||||
//"Qui serait plus susceptible de vivre à Paris",
|
||||
//"Qui est le/la plus gourmand·e ?",
|
||||
//"Qui a vote le plus à droite ?",
|
||||
//"Qui cuisine le mieux ?",
|
||||
//"Qui pourrait passer son temps à dormir ?",
|
||||
//"Qui est la plus petite tasse ?",
|
||||
//"Qui est le/la plus sportif·ve ?",
|
||||
//"Qui a le meilleur sens de l'humour ?",
|
||||
//"Qui est le/la plus ponctuel·le ?",
|
||||
//"Qui est le/la plus aventureux·se ?",
|
||||
//"Qui cuisine le mieux ?",
|
||||
//"Qui a le plus de connaissances en musique ?",
|
||||
//"Qui est le/la plus artistique ?",
|
||||
//"Qui est le/la plus sociable ?",
|
||||
//"Qui est le/la plus compétitif·ve ?",
|
||||
//"Qui est le/la plus susceptible de devenir célèbre ?",
|
||||
//"Qui est le/la plus généreux·se ?",
|
||||
//"Qui a le plus de chances de voyager dans l'espace ?",
|
||||
//"Qui a les meilleures compétences en survie ?",
|
||||
//"Qui a le plus d'animaux de compagnie ?",
|
||||
//"Qui est le/la plus organisé·e ?",
|
||||
//"Qui est le/la plus romantique ?",
|
||||
//"Qui est le/la plus économe ?",
|
||||
//"Qui a le plus de style vestimentaire ?",
|
||||
//"Qui est le/la plus fêtard·e ?",
|
||||
//"Qui a le plus de talents cachés ?",
|
||||
//"Qui est le/la plus technophile ?",
|
||||
//"Qui est le/la plus écolo ?",
|
||||
//"Qui est le/la plus doué·e pour les langues étrangères ?",
|
||||
//"Qui a le plus de chances de gagner un marathon ?",
|
||||
//"Qui est le/la plus passionné·e de lecture ?",
|
||||
//"Qui est le/la plus doué·e pour le bricolage ?",
|
||||
//"Qui est le/la plus susceptible de réussir en affaires ?",
|
||||
//"Qui a le plus de connaissances en cinéma ?",
|
||||
//"Qui est le/la plus spirituel·le ?",
|
||||
//"Qui est le/la plus susceptible de vivre à l'étranger ?",
|
||||
//"Qui est le/la plus drôle ?",
|
||||
//"Qui est le/la plus susceptible de participer à une émission de télé-réalité ?",
|
||||
//"Qui a le plus de patience ?",
|
||||
//"Qui est le/la plus passionné·e de jeux vidéo ?",
|
||||
//"Qui est le/la plus susceptible de devenir un·e chef·fe renommé·e ?",
|
||||
//"Qui est le/la plus doué·e pour les jeux de société ?",
|
||||
//"Qui a le plus de connaissances historiques ?",
|
||||
//"Qui est le/la plus passionné·e de jardinage ?",
|
||||
//"Qui a les meilleurs talents d'acteur·rice ?",
|
||||
"Qui va sauter dans la piscine ?",
|
||||
"Qui date le plus âgé ?",
|
||||
"Qui est le plus flexible avec démonstration ?",
|
||||
"Qui va faire un striptease ?",
|
||||
"Qui sera la pire parent ?",
|
||||
"Qui est le plus alcoolique ?",
|
||||
"Qui serait capable de chier dans la seine ?",
|
||||
"Qui va montrer son dernier message envoyé ?",
|
||||
"Qui va faire une roulette de photos ?",
|
||||
"Qui a déjà bouffé les deux boules d'un mec ?",
|
||||
"Qui va nous décrire son crush ?",
|
||||
"Qui est le plus addict ?",
|
||||
"Qui est le plus croquant ?",
|
||||
"Qui s'auto-suce le plus ?",
|
||||
"Qui va prendre une fessée avec la tongue de l'archi Vergnaud ?",
|
||||
"Qui est le plus à droite ?",
|
||||
"Qui conduit le mieux ?",
|
||||
"Qui gagne au bras de fer ? (Preuve)",
|
||||
"Qui va faire une roulade ?",
|
||||
"Qui a le plus une tête de con•ne ?",
|
||||
"Qui est le meilleur menteur ?",
|
||||
"Qui rikou ?",
|
||||
"Qui va boire un shot d'huile ?",
|
||||
"Qui va manger une cuillère a soupe de mayonnaise ?",
|
||||
"Qui va choisir ce que l'autre va manger ?",
|
||||
"Qui va nous organiser les prochaines vacances ?",
|
||||
"Qui a la plus belle fesse ?",
|
||||
"Qui est le plus intrusif ?",
|
||||
"Qui a le plus haut bodycount ?",
|
||||
"Qui va imiter Nicolas Pham ?",
|
||||
"Qui va dire vinaigrette sans Vi ?",
|
||||
"Qui va faire son âge divisé par deux en pompes ?",
|
||||
"Qui va faire l'intro pour le dernier tiktok ?",
|
||||
"Qui va faire une photo sexy avec son voisin de droite ?",
|
||||
"Qui va brouter de l'herbe ?",
|
||||
"Qui va boire un Ricard piscine ?",
|
||||
"Qui est susceptible de se marier ?",
|
||||
"À qui donneriez vous vos enfants ?",
|
||||
"Qui serait le meilleur DDP ?",
|
||||
"À qui tu te confierais le plus ?",
|
||||
"Qui est le plus béru ?",
|
||||
"Qui est le plus kinky ?",
|
||||
"Qui va nous chanter le menu ?",
|
||||
"Qui lance un monôme tout nu ?",
|
||||
"Qui est le plus rapide ?",
|
||||
"Qui est le plus susceptible d'être célèbre ?",
|
||||
"Qui est le plus romantique ?",
|
||||
"Qui va raconter la chose la plus folle de sa vie ?",
|
||||
"Qui se masturbe le plus ?",
|
||||
"Qui est le plus gros fan de star wars ?",
|
||||
"Qui a pleuré en dernier ?",
|
||||
]
|
||||
|
||||
export function getRandomQuestion(alreadyDone = []){
|
||||
let questionIndex = Math.floor(Math.random() * questions.length)
|
||||
while(alreadyDone.includes(questionIndex)){
|
||||
questionIndex = Math.floor(Math.random() * questions.length)
|
||||
}
|
||||
return {alreadyDone: [...alreadyDone, questionIndex], question: questions[questionIndex]}
|
||||
}
|
7
app/action.ts
Normal file
7
app/action.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
'use server'
|
||||
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function navigate(data: FormData) {
|
||||
redirect(`/${data.get('id')}`)
|
||||
}
|
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
25
app/globals.css
Normal file
25
app/globals.css
Normal file
|
@ -0,0 +1,25 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
22
app/layout.tsx
Normal file
22
app/layout.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
63
app/page.tsx
Normal file
63
app/page.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
'use client'
|
||||
|
||||
import { navigate } from './action'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { IconPencilMinus } from "@tabler/icons-react"
|
||||
|
||||
export default function Home() {
|
||||
const [name, setName] = useState("")
|
||||
const modal = useRef()
|
||||
const inputName = useRef()
|
||||
|
||||
function setUsername() {
|
||||
setName(inputName.current.value)
|
||||
localStorage.setItem("name", inputName.current.value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let localName = localStorage.getItem("name")
|
||||
if(localName != null){
|
||||
setName(localName)
|
||||
} else {
|
||||
modal.current.showModal()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<main data-theme="light" className="flex min-h-screen flex-col items-center space-y-16 p-24">
|
||||
<dialog ref={modal} className="modal">
|
||||
<div className="modal-box">
|
||||
<label className="form-control w-full max-w-xs">
|
||||
<div className="label">
|
||||
<span className="label-text">What is your name?</span>
|
||||
</div>
|
||||
<input type="text" ref={inputName} defaultValue={name} placeholder="Name" className="input input-bordered w-full max-w-xs" />
|
||||
</label>
|
||||
<div className="modal-action">
|
||||
<form method="dialog">
|
||||
{/* if there is a button in form, it will close the modal */}
|
||||
<button onClick={setUsername} className="btn">Close</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<h1 className="text-3xl">Bazaar</h1>
|
||||
<div className="flex flex-col space-y-4 items-center">
|
||||
<div className="flex flex-row space-x-4">
|
||||
<span className="text-xl">{name}</span>
|
||||
<button onClick={() => { modal.current.showModal()}}>
|
||||
<IconPencilMinus />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 items-center">
|
||||
<a href="/random" className="btn btn-primary">Create a room</a>
|
||||
<div className="divider"></div>
|
||||
<form action={navigate} className="flex flex-col space-y-4">
|
||||
<input type="text" name="id" placeholder="Room pin" className="input input-bordered w-full max-w-xs" />
|
||||
<button className="btn btn-primary">Join Room</button>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
9
app/random/page.tsx
Normal file
9
app/random/page.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { redirect } from 'next/navigation'
|
||||
|
||||
export default function Home() {
|
||||
let roomNumber = ""
|
||||
for(let i = 0; i < 6; i++) {
|
||||
roomNumber += Math.floor(Math.random() * 9.99)
|
||||
}
|
||||
redirect('/' + roomNumber.toString())
|
||||
}
|
4
next.config.mjs
Normal file
4
next.config.mjs
Normal file
|
@ -0,0 +1,4 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
export default nextConfig;
|
5556
package-lock.json
generated
Normal file
5556
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
31
package.json
Normal file
31
package.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "bazaar",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "node server.mjs",
|
||||
"build": "next build",
|
||||
"start": "NODE_ENV='production' && node server.mjs",
|
||||
"lint": "next lint",
|
||||
"websocket": "node server.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/icons-react": "^3.11.0",
|
||||
"next": "14.2.5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"socket.io": "^4.7.5",
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"daisyui": "^4.12.10",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.5",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
8
postcss.config.mjs
Normal file
8
postcss.config.mjs
Normal file
|
@ -0,0 +1,8 @@
|
|||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
62
server.mjs
Normal file
62
server.mjs
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { createServer } from "node:http";
|
||||
import next from "next";
|
||||
import { Server } from "socket.io";
|
||||
|
||||
const dev = process.env.NODE_ENV !== "production";
|
||||
const hostname = "localhost";
|
||||
const port = 3000;
|
||||
// when using middleware `hostname` and `port` must be provided below
|
||||
const app = next({ dev, hostname, port });
|
||||
const handler = app.getRequestHandler();
|
||||
|
||||
let active_rooms = {}
|
||||
|
||||
app.prepare().then(() => {
|
||||
const httpServer = createServer(handler);
|
||||
|
||||
const io = new Server(httpServer);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log("User connected " + socket.id)
|
||||
socket.on('room_connect', (room) => {
|
||||
if(!Object.keys(active_rooms).includes(room.id)){
|
||||
console.log("First person joined " + room.id + " ! " + room.name + " is owner.")
|
||||
active_rooms[room.id] = [{id: socket.id, name: room.name, role: "owner", vote: ""}]
|
||||
socket.emit("room_joined", {"room_users": active_rooms[room.id], "role": "owner"})
|
||||
} else {
|
||||
socket.to(room.id).emit("new_player",{"id": socket.id, "name": room.name, role: "player"})
|
||||
active_rooms[room.id].push({id: socket.id, name: room.name, role: "player", vote: ""})
|
||||
socket.emit("room_joined", {"room_users": active_rooms[room.id], role: "player"})
|
||||
console.log("New person joined " + room.id + " ! " + room.name + " is player.")
|
||||
}
|
||||
socket.join(room.id)
|
||||
})
|
||||
|
||||
socket.on("start_game", (params) => {
|
||||
socket.to(params.roomId).emit("start_game",{possibleChoice: params.possibleChoice, question: params.question, duration: params.duration})
|
||||
})
|
||||
|
||||
socket.on("reset_game", (params) => {
|
||||
socket.to(params.roomId).emit("reset_game")
|
||||
})
|
||||
|
||||
socket.on("next_question", (params) => {
|
||||
socket.to(params.roomId).emit("next_question",{possibleChoice: params.possibleChoice, question: params.question, duration: params.duration})
|
||||
})
|
||||
|
||||
socket.on("player_choice", (params) => {
|
||||
console.log(params)
|
||||
socket.to(params.roomId).emit("player_choice", {choice: params.choice, player: params.player})
|
||||
})
|
||||
// ...
|
||||
});
|
||||
|
||||
httpServer
|
||||
.once("error", (err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
})
|
||||
.listen(port, () => {
|
||||
console.log(`> Ready on http://${hostname}:${port}`);
|
||||
});
|
||||
});
|
25
tailwind.config.ts
Normal file
25
tailwind.config.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('daisyui'),
|
||||
],
|
||||
daisyui: {
|
||||
themes: ["light", "dark", "cupcake"],
|
||||
},
|
||||
};
|
||||
export default config;
|
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in a new issue