Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of the lobby room component #12

Merged
merged 3 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Auth } from "./pages/Auth"
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"
import { Home } from "./pages/Home"
import { QuestionCreator } from "./pages/QuestionCreator/QuestionCreator"
import { GameController } from "./pages/game/GameController"
import { Oups } from "./pages/Oups"

function App() {
return (
Expand All @@ -10,7 +12,9 @@ function App() {
<Routes>
<Route path="/" element={<Home />} />
<Route path="/signin" element={<Auth />} />
<Route path="/creation" element={<QuestionCreator />} />
<Route path="/questions" element={<QuestionCreator />} />
<Route path="/games/:gameId" element={<GameController />} />
<Route path="/error" element={<Oups />} />
</Routes>
</Router>
</div>
Expand Down
31 changes: 31 additions & 0 deletions src/components/JoinGame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useNavigate } from "react-router-dom"
import { createGame } from "../services/games-store"
import { useState } from "react"

export const JoinGame = () => {
const [joinGameId, setJoinGamemId] = useState("")
const navigate = useNavigate()

const joinGameHandler = async () => {
navigate(`/games/${joinGameId}`)
}
const newGameHandler = async () => {
const newGameId = await createGame()
navigate(`/games/${newGameId}`)
}

return (
<div>
<div>
<input
placeholder="Code"
onChange={(e) => setJoinGamemId(e.target.value)}
/>
<button onClick={joinGameHandler}>Rejoindre partie</button>
</div>
<div>
<button onClick={newGameHandler}>Nouvelle partie</button>
</div>
</div>
)
}
2 changes: 1 addition & 1 deletion src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const Navbar = () => {
return (
<div>
<Link to={"/"}>Home</Link>
<Link to={"/creation"}>Créer des questions</Link>
<Link to={"/questions"}>Créer des questions</Link>
<Link to={"/signin"}>Connexion</Link>
</div>
)
Expand Down
2 changes: 2 additions & 0 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { JoinGame } from "../components/JoinGame"
import { Navbar } from "../components/Navbar"

export const Home = () => {
Expand All @@ -6,6 +7,7 @@ export const Home = () => {
<Navbar />
<h1>Rculture</h1>
<p>A game about culture, quiz and chill</p>
<JoinGame />
</div>
)
}
11 changes: 11 additions & 0 deletions src/pages/Oups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Link } from "react-router-dom"

export const Oups = () => {
return (
<div>
<Link to={"/"}>Revenir à l'accueil</Link>
<h1>Oups!</h1>
<p>C'est surement un petit rien...</p>
</div>
)
}
2 changes: 1 addition & 1 deletion src/pages/QuestionCreator/ChoiceQuestionCreator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react"
import { createQuestion } from "../../services/store"
import { createQuestion } from "../../services/questions-store"
import { initializeEmptyQuestionFields, splitAndTrim } from "../../utils/utils"

export const ChoiceQuestionCreator = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/QuestionCreator/CompleteQuestionCreator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react"
import { createQuestion } from "../../services/store"
import { createQuestion } from "../../services/questions-store"
import { initializeEmptyQuestionFields, splitAndTrim } from "../../utils/utils"

export const CompleteQuestionCreator = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/QuestionCreator/SimpleQuestionCreator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react"
import { createQuestion } from "../../services/store"
import { createQuestion } from "../../services/questions-store"
import { initializeEmptyQuestionFields, splitAndTrim } from "../../utils/utils"

export const SimpleQuestionCreator = () => {
Expand Down
74 changes: 74 additions & 0 deletions src/pages/game/GameController.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Navbar } from "../../components/Navbar"
import { LobbySettings } from "./LobbySettings"
import { LobbyPlayers } from "./LobbyPlayers"
import { useNavigate, useParams } from "react-router-dom"
import { useState } from "react"
import { addPlayerToGame, listenGame } from "../../services/games-store"
import { getSnapshotData } from "../../services/store"
import { Game, GameSchema, StoreResponse } from "../../utils/types"
import { GameQuestion } from "./GameQuestion"
import { getUserInfo } from "../../services/authentication"

export const GameController = () => {
const [isSetup, setIsSetup] = useState(false)
const [questionIndex, setQuestionIndex] = useState(0)
const [usernames, setUsernames] = useState([] as string[])

const { gameId } = useParams()
const navigate = useNavigate()

if (gameId === undefined) {
console.error("Undefined game id")
navigate("/error/")
return
}

// Add user to game
addPlayerToGame(gameId, getUserInfo())

// Listen to game changes
listenGame(gameId, (snapshot) => {
const response: StoreResponse<Game> = getSnapshotData(snapshot, GameSchema)

// Error handler
if (!response.success) {
console.error(response.error)
navigate("/error/")
return
}
if (response.data.length !== 1) {
console.error("Number of games must be 1")
navigate("/error/")
return
}

// Get this game
const game = response.data.shift()!

// Update users
setUsernames(Object.values(game.users))

// Control game progression state
if (isSetup !== game.isSetup) {
setIsSetup(game.isSetup)
}
if (questionIndex !== game.questionIndex) {
setQuestionIndex(game.questionIndex)
}
})

return (
<div>
<Navbar />
{isSetup ? (
<GameQuestion />
) : (
<div>
<h1>Nouvelle partie</h1>
<LobbySettings gameId={gameId} />
<LobbyPlayers gameId={gameId} usernames={usernames} />
</div>
)}
</div>
)
}
7 changes: 7 additions & 0 deletions src/pages/game/GameQuestion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const GameQuestion = () => {
return (
<div>
<h2>Partie!</h2>
</div>
)
}
18 changes: 18 additions & 0 deletions src/pages/game/LobbyPlayers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type LobbyPlayersProps = {
gameId: string
usernames: string[]
}

export const LobbyPlayers = (props: LobbyPlayersProps) => {
return (
<div>
<h2>Joueurs</h2>
<div>Game id : {props.gameId}</div>
<ul>
{props.usernames.map((username) => (
<li>{username}</li>
))}
</ul>
</div>
)
}
20 changes: 20 additions & 0 deletions src/pages/game/LobbySettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { startGame } from "../../services/games-store"

type LobbySettingsProps = {
gameId: string
}

export const LobbySettings = (props: LobbySettingsProps) => {
const startGameHandler = async () => {
await startGame(props.gameId)
}

return (
<div>
<h2>Parametres</h2>
<div>Game id : {props.gameId}</div>
<input placeholder="Themes" />
<button onClick={startGameHandler}>Commencer</button>
</div>
)
}
8 changes: 2 additions & 6 deletions src/services/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
createUserWithEmailAndPassword,
signInWithPopup,
signOut,
} from "firebase/auth"
import { createUserWithEmailAndPassword, signInWithPopup, signOut } from "firebase/auth"
import { auth, googleAuthProvider } from "../config/firebase"
import { UserInfo } from "../utils/types"
import { initializeAnonymousUserInfo } from "../utils/utils"
Expand Down Expand Up @@ -68,7 +64,7 @@ export function getUserInfo(): UserInfo {
const userInfoJson = localStorage.getItem("auth")

if (userInfoJson === null) {
return {} as UserInfo
return initializeAnonymousUserInfo()
}

return JSON.parse(userInfoJson) as UserInfo
Expand Down
80 changes: 80 additions & 0 deletions src/services/games-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
Query,
QuerySnapshot,
addDoc,
collection,
doc,
documentId,
onSnapshot,
query,
updateDoc,
where,
} from "firebase/firestore"
import { db } from "../config/firebase"
import { getErrorStoreResponse, getSuccessStoreResponse, initializeEmptyGameData } from "../utils/utils"
import { findDataByQuery } from "./store"
import { Game, GameSchema, StoreResponse, UserInfo } from "../utils/types"
import { validateStoreResponseLength } from "./validation"

const gamesRef = collection(db, "games")

/* Basic CRUD methods */

async function findGameByQuery(q: Query) {
const response: StoreResponse<Game> = await findDataByQuery(q, GameSchema)
return response
}

export async function findAllGames() {
const q = query(gamesRef)
const games = await findGameByQuery(q)
return games
}

export async function findGameById(id: string) {
const q = query(gamesRef, where(documentId(), "==", id))
let response = await findGameByQuery(q)

response = validateStoreResponseLength(response, 1)
return response
}

export async function createGame() {
const newGame = initializeEmptyGameData()
const gameRef = await addDoc(gamesRef, newGame)
return gameRef.id
}

export async function updateGame(id: string, data: object): Promise<StoreResponse<Game>> {
if (!(await existsGameById(id))) {
return getErrorStoreResponse(`Game ${id} does not exist`)
}

// Update data
const gameRef = doc(db, `games/${id}`)
// await setDoc(gameRef, data, { merge: true })
await updateDoc(gameRef, data)
return getSuccessStoreResponse([])
}

export async function existsGameById(id: string) {
const response = await findGameById(id)
return response.success
}

/* Domain methods */

export async function startGame(id: string) {
const response = await updateGame(id, { isSetup: true })
return response
}

export async function addPlayerToGame(gameId: string, userInfo: UserInfo) {
const response = await updateGame(gameId, { ["users." + userInfo.id]: userInfo.name })
return response
}

export async function listenGame(id: string, callback: (snapshot: QuerySnapshot) => void) {
const q = query(gamesRef, where(documentId(), "==", id))
onSnapshot(q, callback)
}
40 changes: 40 additions & 0 deletions src/services/questions-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Query, addDoc, collection, query, where } from "firebase/firestore"
import { db } from "../config/firebase"
import { Question, QuestionSchema } from "../utils/types"
import { isValideQuestion } from "./validation"
import { findDataByQuery } from "./store"

// References to the collections
const questionsRef = collection(db, "questions")

async function findQuestionByQuery(q: Query) {
const questions = await findDataByQuery(q, QuestionSchema)
return questions
}

export async function findAllQuestions() {
const q = query(questionsRef)
const data = await findQuestionByQuery(q)
return data
}

export async function findQuestionById(questionId: string) {
const q = query(questionsRef, where("id", "==", questionId))
const data = await findQuestionByQuery(q)
return data
}

export async function findQuestionByTags(tags: string[]) {
const q = query(questionsRef, where("tags", "array-contains-any", tags))
const data = await findQuestionByQuery(q)
return data
}

export const createQuestion = async (question: Question) => {
if (!isValideQuestion(question)) {
return
}

const data = await addDoc(questionsRef, question)
return data.id
}
Loading
Loading