diff --git a/webapp/public/british-flag.png b/webapp/public/british-flag.png new file mode 100644 index 00000000..96703563 Binary files /dev/null and b/webapp/public/british-flag.png differ diff --git a/webapp/public/spanish-flag.png b/webapp/public/spanish-flag.png new file mode 100644 index 00000000..bb809d40 Binary files /dev/null and b/webapp/public/spanish-flag.png differ diff --git a/webapp/src/App.tsx b/webapp/src/App.tsx index eb70489f..d344643a 100644 --- a/webapp/src/App.tsx +++ b/webapp/src/App.tsx @@ -1,10 +1,19 @@ +import React from 'react'; import { BrowserRouter } from "react-router-dom"; import { AppRouter } from "./Router"; +import { useTranslation } from 'react-i18next'; /** The old code is not in /pages/init/index.tsx and is shown as default */ function App() { + const { i18n } = useTranslation(); + + React.useEffect(() => { + localStorage.setItem("lang", navigator.language); + i18n.changeLanguage(navigator.language); + }, [i18n]); + return ( diff --git a/webapp/src/common/Nav.test.js b/webapp/src/common/Nav.test.js index 722402c1..d5f9caa6 100644 --- a/webapp/src/common/Nav.test.js +++ b/webapp/src/common/Nav.test.js @@ -1,24 +1,23 @@ import React from 'react'; import { render, fireEvent, getByTestId } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; // Importa MemoryRouter +import { BrowserRouter } from 'react-router-dom'; // Importa MemoryRouter import NavBar from './Nav'; describe('NavBar Component', () => { it('should render without crashing', () => { const { getByTestId } = render( - {/* Envuelve el componente en MemoryRouter */} + {/* Envuelve el componente en MemoryRouter */} - + ); const appName = getByTestId('app_name'); // Reemplaza 'app_name' con el texto real del nombre de la aplicación expect(appName).toBeInTheDocument(); }); -/** it('should navigate to "/game" when "Game" button is clicked', () => { const { getByTestId } = render( - + - + ); const gameButton = getByTestId('nav_game'); // Reemplaza 'nav_game' con el botón de juego fireEvent.click(gameButton); @@ -27,25 +26,15 @@ describe('NavBar Component', () => { it('should navigate to "/groups" when "Groups" button is clicked', () => { const { getByTestId } = render( - + - + ); const groupsButton = getByTestId('nav_groups'); // Reemplaza 'nav_groups' con el botón de grupos fireEvent.click(groupsButton); + console.log(window.location.pathname) expect(window.location.pathname).toBe('/groups'); }); - it('should navigate to "/scoreboard" when "Scoreboard" button is clicked', () => { - const { getByTestId: getByTestId } = render( - - - - ); - const scoreboardButton = getByTestId('nav_scoreboard'); // Reemplaza 'nav_scoreboard' con el botón de marcador - fireEvent.click(scoreboardButton); - expect(window.location.pathname).toBe('/scoreboard'); - }); -**/ // Agrega más pruebas similares para los otros botones y funcionalidades del componente NavBar }); \ No newline at end of file diff --git a/webapp/src/common/Nav.tsx b/webapp/src/common/Nav.tsx index 05884185..30f843fa 100644 --- a/webapp/src/common/Nav.tsx +++ b/webapp/src/common/Nav.tsx @@ -2,19 +2,20 @@ import React, { useState, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import './nav.scss'; import { useTranslation } from 'react-i18next'; -import {AppBar, Container, Toolbar, Grid, Stack, Button, Menu, MenuItem} from "@mui/material"; +import { AppBar, Container, Toolbar, Grid, Stack, Button, Menu, MenuItem, Switch } from "@mui/material"; import { useNavigate } from "react-router-dom"; const NavBar: React.FC<{}> = () => { const location = useLocation(); - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const navigate = useNavigate(); const value:string= JSON.stringify(localStorage.getItem("isAuthenticated")).replace("\"","").replace("\"",""); const user = JSON.stringify(localStorage.getItem("username")).replace("\"", "").replace("\"", ""); const [anchorEl, setAnchorEl] = useState(null); const [open, setOpen] = useState(false); - const [chevronRotated, setChevronRotated] = useState(false); + const [chevronRotated, setChevronRotated] = useState(true); + const [checked, setChecked] = useState(navigator.language==="es-ES"); const handleClick = (event: React.MouseEvent | React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -28,6 +29,20 @@ const NavBar: React.FC<{}> = () => setChevronRotated(false); }; + const handleSwitch = () => { + const language = localStorage.getItem("lang"); + if(language === "es" || language === null){ + localStorage.setItem("lang", "en"); + i18n.changeLanguage("en"); + setChecked(false); + } + else{ + localStorage.setItem("lang", "es"); + i18n.changeLanguage("es"); + setChecked(true) + } + }; + if(value === "false"){ navigate("/"); } @@ -35,18 +50,18 @@ const NavBar: React.FC<{}> = () => useEffect(() => { switch (location.pathname) { case '/game': - document.title = 'Conocer y Vencer - Game'; + document.title = t('app_name') + ' - ' + t('nav_game'); break; case '/groups': - document.title = 'Conocer y Vencer - Groups'; + document.title = t('app_name') + ' - ' + t('nav_groups'); break; case '/profile': - document.title = 'Conocer y Vencer - Profile'; + document.title = t('app_name') + ' - ' + t('nav_profile'); break; default: - document.title = 'Conocer y Vencer'; + document.title = t('app_name'); } - }, [location.pathname]); + }, [location.pathname, t]); return ( = () => > -
+
navigate("/game")}> {t('app_name')}
- - - @@ -90,15 +104,19 @@ const NavBar: React.FC<{}> = () => > + +
  • + +
  • +
    - {currentView === "Game" ? : currentView === "Group" ? : - } + {currentView === "Game" ? : }
    ); diff --git a/webapp/src/components/game-layout/GameLayout.scss b/webapp/src/components/game-layout/game-layout.scss similarity index 100% rename from webapp/src/components/game-layout/GameLayout.scss rename to webapp/src/components/game-layout/game-layout.scss diff --git a/webapp/src/components/game/PlayingGame.tsx b/webapp/src/components/game/PlayingGame.tsx index 28b6fcc7..76ef47fa 100644 --- a/webapp/src/components/game/PlayingGame.tsx +++ b/webapp/src/components/game/PlayingGame.tsx @@ -1,10 +1,11 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react' import { Player, Question4Answers } from './singleplayer/GameSinglePlayer' import axios from 'axios'; +import { useTranslation } from 'react-i18next'; import shuffleAnswers from './util/SuffleAnswers'; import calculatePoints from './util/CalculatePoints'; import { SocketProps } from './multiplayer/GameMultiPlayer'; -import "./QuestionsGame.scss" +import "./questions-game.scss" interface PlayingGameProps { questions: Question4Answers[] @@ -27,6 +28,7 @@ const PlayingGame: FC = ({questions, setCurrentStage, setPlaye const [seconds, setSeconds] = useState(10); const [isWaiting, setIsWaiting] = useState(false); + const { t } = useTranslation(); const answersShuffled = useMemo(() => shuffleAnswers(questions), [questions]); @@ -143,10 +145,10 @@ const PlayingGame: FC = ({questions, setCurrentStage, setPlaye )} {currentQuestion+1 === questions.length && ( <> -

    You answered {correctAnswers} out of {questions.length} questions correctly.

    -

    You earned {calculatePoints(correctAnswers, questions.length)} points.

    +

    {t('playing_single_player_you_answered')}{correctAnswers}{t('playing_single_player_out_of')}{questions.length}{t('playing_single_player_questions_correctly')}

    +

    {t('playing_single_player_you_earned')}{calculatePoints(correctAnswers, questions.length)}{t('playing_single_player_points')}

    {partyCode &&

    Waiting for the rest of the players to finish...

    } - {players &&} + {players &&} )} diff --git a/webapp/src/components/game/ScoreboardGame.tsx b/webapp/src/components/game/ScoreboardGame.tsx index 5c13d05a..8dbc4349 100644 --- a/webapp/src/components/game/ScoreboardGame.tsx +++ b/webapp/src/components/game/ScoreboardGame.tsx @@ -1,7 +1,8 @@ import { FC} from 'react' import { Player } from './singleplayer/GameSinglePlayer'; -import './ScoreboardGame.css'; +import './scoreboard-game.scss'; import { PlayerWithPoints } from './multiplayer/GameMultiPlayer'; +import { useTranslation } from 'react-i18next'; interface ScoreboardGameProps { userScoresSinglePlayer?: Player[]; @@ -17,17 +18,15 @@ const ScoreboardGame:FC = ({userScoresSinglePlayer, userSco } else if (userScoresMultiPlayer){ sorted = userScoresMultiPlayer.sort((a, b) => b.points - a.points); } + const { t } = useTranslation(); return ( - + - - - + + + diff --git a/webapp/src/components/game/LobbyGame.scss b/webapp/src/components/game/lobby-game.scss similarity index 81% rename from webapp/src/components/game/LobbyGame.scss rename to webapp/src/components/game/lobby-game.scss index 973e5a43..7f0a53f4 100644 --- a/webapp/src/components/game/LobbyGame.scss +++ b/webapp/src/components/game/lobby-game.scss @@ -26,8 +26,8 @@ table { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; - align-items: center; /* Vertically center the grid items */ - justify-items: center; /* Horizontally center the grid items */ + align-items: center; + justify-items: center; border-bottom: 1px solid #eee; padding: 10px; } @@ -40,7 +40,7 @@ table { } img { - max-width: 100px; // Cambia el tamaño máximo de la imagen según sea necesario + max-width: 100px; height: auto; display: block; } @@ -107,9 +107,9 @@ table { } .start-game-button:disabled { - background-color: #cccccc; /* Change background color for disabled state */ - color: #666666; /* Change text color for disabled state */ - cursor: not-allowed; /* Change cursor for disabled state */ + background-color: #cccccc; + color: #666666; + cursor: not-allowed; } .lobby-container { diff --git a/webapp/src/components/game/multiplayer/GameMultiPlayer.tsx b/webapp/src/components/game/multiplayer/GameMultiPlayer.tsx index 26e8b640..4c86ed01 100644 --- a/webapp/src/components/game/multiplayer/GameMultiPlayer.tsx +++ b/webapp/src/components/game/multiplayer/GameMultiPlayer.tsx @@ -100,7 +100,7 @@ const GameMultiPlayer: FC = () => { return () => { newSocket.close(); }; - }, []); + }, [SERVER_URL]); const handleCurrentStage = (n:number) => { setStage(n); @@ -111,7 +111,7 @@ const GameMultiPlayer: FC = () => { }; return ( - + {stage === 1 && } {stage === 2 && } {stage === 3 && } diff --git a/webapp/src/components/game/multiplayer/LobbyMultiPlayer.tsx b/webapp/src/components/game/multiplayer/LobbyMultiPlayer.tsx index 3f7522fe..e135d86b 100644 --- a/webapp/src/components/game/multiplayer/LobbyMultiPlayer.tsx +++ b/webapp/src/components/game/multiplayer/LobbyMultiPlayer.tsx @@ -1,29 +1,32 @@ import { FC, useState } from 'react' import { SocketProps, UserPlayer } from './GameMultiPlayer'; -import '../LobbyGame.scss'; +import '../lobby-game.scss'; import axios from 'axios'; +import { useTranslation } from 'react-i18next'; interface LobbyMultiPlayerProps { - socket: SocketProps; - handleCurrentStage: (n: number) => void - partyCode: string - users: UserPlayer[] + socket: SocketProps; + handleCurrentStage: (n: number) => void + partyCode: string + users: UserPlayer[] } -const LobbyMultiPlayer: FC = ({socket, handleCurrentStage, partyCode, users}) => { +const LobbyMultiPlayer: FC = ({ socket, handleCurrentStage, partyCode, users }) => { const [isFetched, setFetched] = useState(true); const uuid = localStorage.getItem("uuid"); + const { t } = useTranslation(); + const fetchQuestions = async () => { setFetched(false) - //const apiEndpoint = 'http://conoceryvencer.xyz:8000' + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; try { const requestData = { - players: users.map((user) => ({uuid: user.uuid})) + players: users.map((user) => ({ uuid: user.uuid })) } const lang = localStorage.getItem("lang") @@ -48,23 +51,23 @@ const LobbyMultiPlayer: FC = ({socket, handleCurrentStage return (
    -

    Lobby - Multiplayer

    -

    Party code: {partyCode}

    +

    {t('lobby_multiplayer_title')}

    +

    {t('lobby_multiplayer_party_code')}{partyCode}

    {users.map((player) => (
    - {player.uuid} + {player.uuid}

    {player.username}

    - {player.isAdmin &&

    Admin

    } - {!player.isAdmin &&

    Player

    } -

    Points: {player.totalPoints}

    + {player.isAdmin &&

    {t('lobby_multiplayer_admin')}

    } + {!player.isAdmin &&

    {t('lobby_multiplayer_player')}

    } +

    {t('lobby_multiplayer_points')}{player.totalPoints}

    ))}
    - - {isFetched && isAdmin() && } - {isFetched && !isAdmin() && } - {!isFetched && } + + {isFetched && isAdmin() && } + {isFetched && !isAdmin() && } + {!isFetched && }
    ) diff --git a/webapp/src/components/game/multiplayer/MenuMultiplayer.css b/webapp/src/components/game/multiplayer/MenuMultiplayer.css deleted file mode 100644 index 9aa4d120..00000000 --- a/webapp/src/components/game/multiplayer/MenuMultiplayer.css +++ /dev/null @@ -1,55 +0,0 @@ -.container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 70vh; - } - .container > * { - margin-bottom: 60px; /* Adjust the margin as needed */ - } - - .create-party-button { - font-size: 1.2em; - padding: 10px 20px; - margin: 10px; - text-decoration: none; - background-color: #007bff; - color: #ffffff; - border: none; - border-radius: 8px; - cursor: pointer; - transition: background-color 0.3s ease; - } - - .join-party-container { - margin-top: 20px; - display: flex; - align-items: center; - } - - .join-party-input { - width: 250px; - height: 35px; - font-size: 16px; - padding: 10px; - margin-right: 10px; - } - - .join-party-button { - font-size: 1.2em; /* Adjust as needed */ - padding: 10px 20px; /* Adjust as needed */ - margin: 10px; /* Adjust as needed */ - text-decoration: none; - background-color: #007bff; - color: #ffffff; /* White text color */ - border: none; - border-radius: 8px; /* Make the button slightly round */ - cursor: pointer; - transition: background-color 0.3s ease; - } - - h1{ - margin-bottom: 20px; - margin-top: 20px; - } \ No newline at end of file diff --git a/webapp/src/components/game/multiplayer/MenuMultiplayer.tsx b/webapp/src/components/game/multiplayer/MenuMultiplayer.tsx index dd1067c3..57bd7d9d 100644 --- a/webapp/src/components/game/multiplayer/MenuMultiplayer.tsx +++ b/webapp/src/components/game/multiplayer/MenuMultiplayer.tsx @@ -1,6 +1,7 @@ import { FC, useState } from 'react' import { SocketProps, UserPlayer } from './GameMultiPlayer'; -import './MenuMultiplayer.css' +import './menu-multiplayer.scss' +import { useTranslation } from 'react-i18next'; interface MenuMultiplayerProps { socket: SocketProps; @@ -14,6 +15,7 @@ const MenuMultiplayer: FC = ({socket, handleCurrentStage, const totalPoints = localStorage.getItem('score'); const uuid = localStorage.getItem('uuid'); const [typedCode, setTypedCode] = useState(); + const { t } = useTranslation(); const createParty = () => { handleCurrentStage(2); @@ -39,13 +41,13 @@ const MenuMultiplayer: FC = ({socket, handleCurrentStage, return (
    -

    Create a party or join one!

    +

    {t('menu_multiplayer_create_or_join')}

    -
    +
    setTypedCode(e.target.value)}> - +
    ) diff --git a/webapp/src/components/game/multiplayer/QuestionsMultiPlayer.tsx b/webapp/src/components/game/multiplayer/QuestionsMultiPlayer.tsx new file mode 100644 index 00000000..bab9c88d --- /dev/null +++ b/webapp/src/components/game/multiplayer/QuestionsMultiPlayer.tsx @@ -0,0 +1,153 @@ +import { FC, useCallback, useEffect, useMemo, useState } from 'react' +import { SocketProps } from './GameMultiPlayer'; +import { Question4Answers } from '../singleplayer/GameSinglePlayer'; +import axios from 'axios'; +import '../questions-game.scss'; +import { useTranslation } from 'react-i18next'; + +interface QuestionsMultiPlayerProps { + socket: SocketProps; + handleCurrentStage: (n: number) => void + questions: Question4Answers[] + partyCode: string +} + + +const QuestionsMultiPlayer: FC = ({socket, questions, partyCode}) => { + + const answersShuffled = useMemo(() => { + return questions.map((question) => { + const answers = [question.correctAnswer, question.incorrectAnswer1, question.incorrectAnswer2, question.incorrectAnswer3]; + for (let i = answers.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [answers[i], answers[j]] = [answers[j], answers[i]]; + } + return answers; + }); + }, [questions]); + + const uuid = localStorage.getItem("userUUID"); + //const apiEndpoint = 'http://conoceryvencer.xyz:8000' + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; + + const [currentQuestion, setCurrentQuestion] = useState(0); + const [correctAnswers, setCorrectAnswers] = useState(0); + const [selectedAnswer, setSelectedAnswer] = useState(null); + const [seconds, setSeconds] = useState(10); + const { t } = useTranslation(); + + const [isWaiting, setIsWaiting] = useState(false); + + const endGame = useCallback(async () => { + const totalPoints = calculatePoints(correctAnswers, questions.length); + const requestData = { + "players": [{ + "uuid": uuid, + "nCorrectAnswers": correctAnswers, + "nWrongAnswers": questions.length - correctAnswers, + "totalScore": totalPoints, + "isWinner": false + }] + }; + + const previousScore = parseInt(localStorage.getItem("score")); + localStorage.setItem("score", (previousScore + totalPoints).toString()); + + await axios.post(`${apiEndpoint}/updateStats`, requestData); + socket.emit('playerFinished', partyCode, totalPoints); + + }, [correctAnswers, questions.length, uuid, apiEndpoint, socket, partyCode]); + + useEffect(() => { + const intervalId = setInterval(async () => { + if((currentQuestion+1) < questions.length){ + if (seconds > 0) { + setSeconds(prevSeconds => prevSeconds - 1); + } else { + setCurrentQuestion(currentQuestion + 1); + setSelectedAnswer(null); + clearInterval(intervalId); + setSeconds(10); + if(currentQuestion+2 === questions.length){ + await endGame() + } + } + } + }, 1000); + + return () => clearInterval(intervalId); + }, [seconds, currentQuestion, questions, endGame]); + + + const handleAnswerClick = async (answer: string, isCorrect:boolean) => { + setSeconds(10); + if(!isWaiting){ + setIsWaiting(true); + setSelectedAnswer(answer); + + setTimeout(() => { + if (isCorrect) { + setCorrectAnswers(correctAnswers + 1); + } + setCurrentQuestion(currentQuestion + 1); + setSelectedAnswer(null); + }, 1000); + setIsWaiting(false); + } + + if(currentQuestion+2 === questions.length){ + await endGame(); + } + }; + + const calculatePoints = (correctAnswers: number, totalQuestions: number) => { + const incorrectQuestions = totalQuestions - correctAnswers; + const points = correctAnswers * 100 - incorrectQuestions * 25; + return points; + } + + const getAnswers = () => { + const answers = answersShuffled[currentQuestion]; + if (answers.length > 4) { + console.log(answers) + const removeCount = answers.length - 4; + answers.splice(0, removeCount); + } + return answersShuffled[currentQuestion]; + }; + + return ( +
    + {(currentQuestion+1) < questions.length && ( + <> +
    +

    Question {currentQuestion + 1} / {questions.length}

    +

    {questions[currentQuestion].question}

    +

    {seconds}

    +
    +
    + {getAnswers().map((answer) => { + const isCorrect = questions[currentQuestion].correctAnswer === answer; + const buttonColor = (selectedAnswer === answer && !isWaiting) ? (isCorrect ? '#66ff66' : '#ff6666') : '#89c3ff'; + return ( + + )} + )} +
    + + )} + {currentQuestion+1 === questions.length && ( + <> +

    {t('questions_multiplayer_you_answered')}{correctAnswers}{t('questions_multiplayer_out_of')}{questions.length}{t('questions_multiplayer_questions_correctly')}

    +

    {t('questions_multiplayer_you_earned')}{calculatePoints(correctAnswers, questions.length)}{t('questions_multiplayer_points')}

    +

    {t('questions_multiplayer_waiting_players')}

    + + )} +
    + ) +} + +export default QuestionsMultiPlayer \ No newline at end of file diff --git a/webapp/src/components/game/multiplayer/menu-multiplayer.scss b/webapp/src/components/game/multiplayer/menu-multiplayer.scss new file mode 100644 index 00000000..c234438d --- /dev/null +++ b/webapp/src/components/game/multiplayer/menu-multiplayer.scss @@ -0,0 +1,46 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; +} + +.create-party-button { + font-size: 1.2em; + padding: 10px 20px; + margin: 10px; + text-decoration: none; + background-color: #007bff; + color: #ffffff; + border: none; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.join-party-input { + width: 250px; + height: 35px; + font-size: 16px; + padding: 10px; + margin-right: 10px; +} + +.join-party-button { + font-size: 1.2em; + padding: 10px 20px; + margin: 10px; + text-decoration: none; + background-color: #007bff; + color: #ffffff; + border: none; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +h1{ + margin-bottom: 20px; + margin-top: 20px; +} \ No newline at end of file diff --git a/webapp/src/components/game/QuestionsGame.scss b/webapp/src/components/game/questions-game.scss similarity index 59% rename from webapp/src/components/game/QuestionsGame.scss rename to webapp/src/components/game/questions-game.scss index 29a81b71..18cc6049 100644 --- a/webapp/src/components/game/QuestionsGame.scss +++ b/webapp/src/components/game/questions-game.scss @@ -17,14 +17,14 @@ .answer-grid button { width: 100%; height: 50px; - border-radius: 10px; /* Rounded borders */ - font-size: 18px; /* Big text */ - font-weight: bold; /* Bold font */ - transition: transform 0.2s; /* Transition for scale animation */ + border-radius: 10px; + font-size: 18px; + font-weight: bold; + transition: transform 0.2s; cursor:pointer; } .answer-grid button:active { - transform: scale(0.95); /* Scale down on active state */ + transform: scale(0.95); } \ No newline at end of file diff --git a/webapp/src/components/game/ScoreboardGame.css b/webapp/src/components/game/scoreboard-game.scss similarity index 100% rename from webapp/src/components/game/ScoreboardGame.css rename to webapp/src/components/game/scoreboard-game.scss diff --git a/webapp/src/components/game/singleplayer/GameSinglePlayer.tsx b/webapp/src/components/game/singleplayer/GameSinglePlayer.tsx index de381b89..cbe6e732 100644 --- a/webapp/src/components/game/singleplayer/GameSinglePlayer.tsx +++ b/webapp/src/components/game/singleplayer/GameSinglePlayer.tsx @@ -4,6 +4,7 @@ import LobbyGame from './LobbyGameSinglePlayer'; import PlayingGame from '../PlayingGame'; import ScoreboardGame from '../ScoreboardGame'; import { Container } from '@mui/material'; +import { useTranslation } from 'react-i18next'; export interface Question4Answers { uuid: string @@ -29,11 +30,12 @@ const GameSinglePlayer = () => { const username = localStorage.getItem("username"); const uuid = localStorage.getItem("userUUID"); const [fetched, setFetched] = useState(false); + const { t } = useTranslation(); useEffect(() => { const fetchQuestions = async () => { - //const apiEndpoint = 'http://conoceryvencer.xyz:8000' - const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; + //const apiEndpoint = 'http://conoceryvencer.xyz:8000' + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; try { setPlayers([ @@ -65,7 +67,7 @@ const GameSinglePlayer = () => { } }, [questions.length, uuid, username, fetched]); - if (!username) return

    Error

    ; + if (!username) return

    {t('game_single_player_error')}

    ; const handlePlayers = (Players:Player[]) => { setPlayers(Players); diff --git a/webapp/src/components/game/singleplayer/LobbyGameSinglePlayer.tsx b/webapp/src/components/game/singleplayer/LobbyGameSinglePlayer.tsx index c54e3bf3..6a7e5810 100644 --- a/webapp/src/components/game/singleplayer/LobbyGameSinglePlayer.tsx +++ b/webapp/src/components/game/singleplayer/LobbyGameSinglePlayer.tsx @@ -1,6 +1,7 @@ import { FC, useRef } from 'react' import { Player } from './GameSinglePlayer'; -import '../LobbyGame.scss'; +import '../lobby-game.scss'; +import { useTranslation } from 'react-i18next'; interface LobbyGameProps { setPlayers: (players:Player[]) => void; @@ -19,7 +20,9 @@ const LobbyGame: FC = ({setPlayers, players, setCurrentStage, is } }; + const botGen = useRef(botCounter()); // Assign the function to the variable + const { t } = useTranslation(); const addBotPlayer = () => { if (players.length < 4) { @@ -37,35 +40,28 @@ const LobbyGame: FC = ({setPlayers, players, setCurrentStage, is return (
    -

    Lobby - Single player

    +

    {t('lobby_single_player_title')}

    {players.map((player, index) => (
    {player.username} -

    {player.username}

    -

    Total points: {player.points}

    - {!player.isBot && ( - - )} +

    {player.username}

    +

    {t('lobby_single_player_total_points')}{player.points}

    {player.isBot && ( - + )}
    ))}
    - - {isFetched && } - {!isFetched && }
    diff --git a/webapp/src/components/game/singleplayer/game.test.js b/webapp/src/components/game/singleplayer/game.test.js index 62a1f072..38c6472e 100644 --- a/webapp/src/components/game/singleplayer/game.test.js +++ b/webapp/src/components/game/singleplayer/game.test.js @@ -55,9 +55,9 @@ describe('GameSinglePlayer component', () => { }; Object.defineProperty(window, 'localStorage', { value: localStorageMock }); - const { getByText } = render(); + const { getByTestId } = render(); // Expect error message to be rendered - expect(getByText('Error')).toBeInTheDocument(); + expect(getByTestId("game_single_player_error")).toBeInTheDocument(); }); }); diff --git a/webapp/src/components/game/singleplayer/lobby.test.js b/webapp/src/components/game/singleplayer/lobby.test.js index f56dc6f1..924cc5e0 100644 --- a/webapp/src/components/game/singleplayer/lobby.test.js +++ b/webapp/src/components/game/singleplayer/lobby.test.js @@ -43,7 +43,7 @@ describe('LobbyGame component', () => { }); it('adds a bot player correctly', () => { - const { getByTestId, getAllByText } = render( + const { getByTestId, getAllByTestId } = render( { fireEvent.click(addBotButton); // Find all elements that contain the text "Bot" - const botElements = getAllByText(/Bot/); + const botElements = getAllByTestId('player-item'); // Check if at least one element containing "Bot" text is found - expect(botElements.length).toBeGreaterThan(0); + expect(botElements.length).toBeGreaterThan(1); }); }); diff --git a/webapp/src/components/group/Group.scss b/webapp/src/components/group/Group.scss index e69de29b..31828464 100644 --- a/webapp/src/components/group/Group.scss +++ b/webapp/src/components/group/Group.scss @@ -0,0 +1,24 @@ +.groups-container { + display: flex !important; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.group-button { + width: 280px; + font-size: 1.5em; + padding: 15px 30px; + margin: 5px; + text-decoration: none; + background-color: #007bff; + color: #ffffff; + border: none; + border-radius: 8px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.group-button:hover { + background-color: #0056b3; +} \ No newline at end of file diff --git a/webapp/src/components/group/GroupCreationModal.tsx b/webapp/src/components/group/GroupCreationModal.tsx index 40ea16ce..6df262b6 100644 --- a/webapp/src/components/group/GroupCreationModal.tsx +++ b/webapp/src/components/group/GroupCreationModal.tsx @@ -21,8 +21,6 @@ export const CreationModal = (props: ActionProps) => { const createGroup = async () =>{ try{ - console.log("Public?"); - console.log(isPublic); await axios.post(`${apiEndpoint}/createGroup`, { groupName, creatorUUID, description, isPublic }).then( res => { props.nowHasGroup(); }); diff --git a/webapp/src/components/group/GroupTable.tsx b/webapp/src/components/group/GroupTable.tsx index de5d400e..4ac519a0 100644 --- a/webapp/src/components/group/GroupTable.tsx +++ b/webapp/src/components/group/GroupTable.tsx @@ -14,7 +14,7 @@ interface Member { role: string; } -let members: Member[] = new Array(); +let members: Member[] = []; let adminUUID = ""; let groupName = ""; @@ -30,17 +30,14 @@ export const GroupTable = (props: TableProps) => { const { t } = useTranslation(); const aFunction = async ()=>{ await axios.get(`${apiEndpoint}/getGroup/`+props.groupUUID).then(res => { - console.log(res.data); - members = new Array(); + members = []; numberMembers=0; total = 0; - console.log(res.data); for(let member of res.data.members){ let memberRole = t('group_table_member'); - if(member.uuid == res.data.admin.uuid){ + if(member.uuid === res.data.admin.uuid){ memberRole = t('group_table_leader'); } - console.log(memberRole); members.push({ username : member.username, totalScore : member.totalScore, @@ -49,7 +46,6 @@ export const GroupTable = (props: TableProps) => { total += +member.totalScore; numberMembers++; } - console.log(members); adminUUID = res.data.admin.uuid; groupName = res.data.groupName; members.sort((member) => (+member.totalScore)); @@ -62,7 +58,6 @@ export const GroupTable = (props: TableProps) => { const expelledUUID = JSON.stringify(localStorage.getItem("userUUID")).replace("\"", "").replace("\"", ""); await axios.post(`${apiEndpoint}/leaveGroup`, { expelledUUID, groupName, adminUUID}).then( res => { props.nowHasNoGroup(); - console.log(res); // add only groups that are public }) } catch (error:any) { @@ -80,16 +75,16 @@ export const GroupTable = (props: TableProps) => { -

    {groupName}

    +

    {groupName}

    -

    {total} points

    +

    {total}{t('group_table_points')}

    -

    {numberMembers} members

    +

    {numberMembers}{t('group_table_members')}

    - +
    @@ -118,6 +113,5 @@ export const GroupTable = (props: TableProps) => {
    )} - ) } \ No newline at end of file diff --git a/webapp/src/components/group/NoGroup.tsx b/webapp/src/components/group/NoGroup.tsx index fc84f30d..66d20792 100644 --- a/webapp/src/components/group/NoGroup.tsx +++ b/webapp/src/components/group/NoGroup.tsx @@ -1,5 +1,5 @@ import './Group.scss'; -import { Button, Container, Snackbar, TextField, Grid, Stack, RadioGroup, FormControlLabel, Radio } from "@mui/material"; +import { Button, Container, Snackbar, Grid, Stack } from "@mui/material"; import { useEffect, useState } from 'react'; import axios from 'axios'; import { CreationModal } from './GroupCreationModal'; @@ -19,16 +19,16 @@ type ActionProps = { nowHasGroup:()=> void; } -let groups: Group[] = new Array(); +let groups: Group[] = []; let groupsCharged = false; const NoGroup = (props: ActionProps) => { - const { t } = useTranslation(); const [error, setError] = useState(''); const [createModal, setCreateModal] = useState(false); const [joinModal, setJoinModal] = useState(false); - + const { t } = useTranslation(); + const creatorUUID = JSON.stringify(localStorage.getItem("userUUID")).replace("\"", "").replace("\"", ""); const toggleCreateModal = () => { @@ -53,13 +53,11 @@ const NoGroup = (props: ActionProps) => try{ await axios.get(`${apiEndpoint}/getGroups`).then( res => { // new array here so in case it is chared twice it doesn't contain dupllicate data - groups = new Array(); + groups = []; for(let group of res.data){ - console.log("Group:"+JSON.stringify(group)); let isPublic = JSON.stringify(group.isPublic).replace("\"", "").replace("\"", ""); // add only groups that are public if(isPublic === "true"){ - console.log(group.members); let theNumMembers = group.members.length; groups.push({ groupName : group.groupName, @@ -70,11 +68,9 @@ const NoGroup = (props: ActionProps) => } } groupsCharged = true; - }) } catch (error:any) { - console.log("error: "+error); - setError(error.response.data.error); + setError(error.response.data.error); } } @@ -92,27 +88,22 @@ const NoGroup = (props: ActionProps) => return ( - - - -

    You are not part of a group...

    - - - + + +

    {t('no_group_not_part')}

    + +
    - {error && ( - setError('')} message={`Error: ${error}`} data-testid="error-snackbar" /> + setError('')} message={`Error: ${error}`} data-testid="error-snackbar"/> )} - {createModal && () } - {joinModal && (groupsCharged && (
    -

    {t('join_group_button')}

    +

    {t('no_group_join_group')}

    {groups.map((group) => ( @@ -123,16 +114,12 @@ const NoGroup = (props: ActionProps) =>

    {group.numMembers}/{group.maxNumUsers}

    - - - +
    ))} - - - +
    diff --git a/webapp/src/components/init/Init.tsx b/webapp/src/components/init/Init.tsx index 2ec55073..8f99622a 100644 --- a/webapp/src/components/init/Init.tsx +++ b/webapp/src/components/init/Init.tsx @@ -15,7 +15,7 @@ const Init = (props:ActionProps) =>{ const handleViewChange = (isLoginView: boolean) => { setCurrentView(isLoginView ? 'login' : 'register'); props.changeView(isLoginView); - document.title = isLoginView ? t('Conocer y Vencer - Login') : t('Conocer y Vencer - Register'); + document.title = isLoginView ? (t('app_name') + '-' + t('login')) : (t('app_name') + '-' + t('register')); }; return ( diff --git a/webapp/src/components/login/Login.test.js b/webapp/src/components/login/Login.test.js index 715c2598..22272c62 100644 --- a/webapp/src/components/login/Login.test.js +++ b/webapp/src/components/login/Login.test.js @@ -41,7 +41,7 @@ describe('Login component', () => { // Wait for the Snackbar to be open await waitFor(() => { - expect(screen.getByText(/Login successful/i)).toBeInTheDocument(); + expect(screen.getByTestId("login-successfull-snackbar")).toBeInTheDocument(); }); // Verify local storage is set correctly @@ -77,7 +77,7 @@ describe('Login component', () => { // Wait for the error Snackbar to be open await waitFor(() => { - expect(screen.getByText(/Error: Internal Server Error/i)).toBeInTheDocument(); + expect(screen.getByTestId('login-error-snackbar')).toBeInTheDocument(); }); // Verify local storage is not set when there's an error diff --git a/webapp/src/components/login/Login.tsx b/webapp/src/components/login/Login.tsx index 3df7ff6f..8b105c8d 100644 --- a/webapp/src/components/login/Login.tsx +++ b/webapp/src/components/login/Login.tsx @@ -1,4 +1,3 @@ -// src/components/Login.js import { useState, KeyboardEvent } from 'react'; import axios from 'axios'; import { Container, Typography, TextField, Snackbar, Button, Stack } from '@mui/material'; @@ -21,8 +20,6 @@ const Login = (props: ActionProps) => { //const apiEndpoint = 'http://conoceryvencer.xyz:8000' const apiEndpoint = process.env.REACT_APP_API_ENDPOINT; - console.log(apiEndpoint); - const handleReturnButtonClick = () => { document.title = "Conocer y Vencer"; props.goBack(); @@ -34,7 +31,6 @@ const Login = (props: ActionProps) => { localStorage.clear(); const user = await axios.post(`${apiEndpoint}/login`, { username, password }); - console.log(user); localStorage.setItem("username", user.data.username); localStorage.setItem("score", user.data.totalScore); localStorage.setItem("nWins", user.data.nWins); @@ -70,14 +66,14 @@ const Login = (props: ActionProps) => { setUsername(e.target.value)} /> setPassword(e.target.value)} @@ -91,9 +87,9 @@ const Login = (props: ActionProps) => { {t('return')} - + {error && ( - setError('')} message={`Error: ${error}`} /> + setError('')} message={`Error: ${error}`} /> )}
    diff --git a/webapp/src/components/register/Register.test.js b/webapp/src/components/register/Register.test.js index 92dd7cb7..548b8ae5 100644 --- a/webapp/src/components/register/Register.test.js +++ b/webapp/src/components/register/Register.test.js @@ -36,7 +36,7 @@ describe('Register component', () => { // Wait for the Snackbar to be open await waitFor(() => { - expect(screen.getByText(/You registered successfully/i)).toBeInTheDocument(); + expect(screen.getByTestId('register-successfull-snackbar')).toBeInTheDocument(); }); }); @@ -63,7 +63,7 @@ describe('Register component', () => { // Wait for the error Snackbar to be open await waitFor(() => { - expect(screen.getByText(/Error: Internal Server Error/i)).toBeInTheDocument(); + expect(screen.getByTestId('register-error-snackbar')).toBeInTheDocument(); }); }); }); diff --git a/webapp/src/components/register/Register.tsx b/webapp/src/components/register/Register.tsx index 26ef2e64..9ff0c4e1 100644 --- a/webapp/src/components/register/Register.tsx +++ b/webapp/src/components/register/Register.tsx @@ -56,7 +56,7 @@ const Register = (props:ActionProps) => { }; const handleReturnButtonClick = () => { - document.title = "Conocer y Vencer"; + document.title = t('app_name'); props.goBack(); }; @@ -75,7 +75,7 @@ const Register = (props:ActionProps) => { name="username" margin="normal" fullWidth - label="Username" + label={t('label_username')} value={username} onChange={(e) => setUsername(e.target.value)} /> @@ -83,7 +83,7 @@ const Register = (props:ActionProps) => { name="password" margin="normal" fullWidth - label="Password" + label={t('label_password')} type="password" value={password} onChange={(e) => setPassword(e.target.value)} @@ -97,9 +97,9 @@ const Register = (props:ActionProps) => { {t('return')}
    - + {error && ( - setError('')} message={`Error: ${error}`} /> + setError('')} message={`Error: ${error}`} /> )}
    ); diff --git a/webapp/src/components/scoreboard/Scoreboard.scss b/webapp/src/components/scoreboard/Scoreboard.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/webapp/src/components/scoreboard/Scoreboard.test.js b/webapp/src/components/scoreboard/Scoreboard.test.js deleted file mode 100644 index eee92c5d..00000000 --- a/webapp/src/components/scoreboard/Scoreboard.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import Scoreboard from './Scoreboard'; - -describe('Scoreboard Component', () => { - it('should render scoreboard component', () => { - const { getByText } = render(); - const headingElement = getByText('Scoreboard'); - const paragraphElement = getByText('Here is the scoreboard'); - - expect(headingElement).toBeInTheDocument(); - expect(paragraphElement).toBeInTheDocument(); - }); -}); \ No newline at end of file diff --git a/webapp/src/components/scoreboard/Scoreboard.tsx b/webapp/src/components/scoreboard/Scoreboard.tsx deleted file mode 100644 index e561e9d5..00000000 --- a/webapp/src/components/scoreboard/Scoreboard.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import './Scoreboard.scss'; - -const Scoreboard = () => -{ - return ( -
    -

    Scoreboard

    -

    Here is the scoreboard

    -
    - ) -} - -export default Scoreboard; \ No newline at end of file diff --git a/webapp/src/i18n.ts b/webapp/src/i18n.ts index 2a8613eb..9bc5a2d6 100644 --- a/webapp/src/i18n.ts +++ b/webapp/src/i18n.ts @@ -16,39 +16,90 @@ i18n login: 'Login', return: 'Return', register: 'Register', + label_username: 'Nombre de usuario', + label_password: 'Contraseña', login_google: 'Login with Google', nav_game: 'Game', nav_groups: 'Groups', nav_scoreboard: 'Scoreboard', nav_profile: 'Profile', + nav_logout: 'Logout', + g_login_button_login_failed: 'Login Failed', + lobby_multiplayer_title: 'Lobby - Multiplayer', + lobby_multiplayer_party_code: 'Party code: ', + lobby_multiplayer_admin: 'Admin', + lobby_multiplayer_player: 'Player', + lobby_multiplayer_points: 'Points: ', + lobby_multiplayer_exit: 'Exit', + lobby_multiplayer_start_game: 'Start game', + lobby_multiplayer_loading_questions: 'Loading questions...', + menu_multiplayer_create_or_join: 'Create a room or join one', + menu_multiplayer_create: 'Create Room', + menu_multiplayer_join: 'Join Room', + questions_multiplayer_question: 'Question ', + questions_multiplayer_you_answered: 'You answered ', + questions_multiplayer_out_of: ' out of ', + questions_multiplayer_questions_correctly: ' questions correctly.', + questions_multiplayer_you_earned: 'You earned ', + questions_multiplayer_points: ' points.', + questions_multiplayer_waiting_players: 'Waiting for the rest of the players to finish...', + game_single_player_error: 'Error', + lobby_single_player_title: 'Lobby - Single Player', + lobby_single_player_total_points: 'Total points: ', + lobby_single_player_delete: 'Delete', + lobby_single_player_add_bot_player: 'Add Bot Player', + lobby_single_player_start_game: 'Start Game', + lobby_single_player_loading_questions: 'Loading Questions...', + playing_single_player_you_answered: 'You answered ', + playing_single_player_out_of: ' out of ', + playing_single_player_questions_correctly: ' questions correctly.', + playing_single_player_you_earned: 'you earned ', + playing_single_player_points: ' points.', + playing_single_player_next: 'Next', + scoreboard_game_game_scoreboard: 'Game Scoreboard', + scoreboard_game_position: 'Position', + scoreboard_game_username: 'Username', + scoreboard_game_points: 'Points', + game_layout_game: 'Game', + game_layout_groups: 'Groups', + game_layout_scoreboard: 'Scoreboard', + group_table_points: ' points', + group_table_members: ' members', + group_table_leave: 'Leave', + group_table_username: 'Username', + group_table_role: 'Role', + group_table_score: 'Score', + no_group_not_part: 'You are not part of a group yet...', + no_group_join: 'Join a group', + no_group_create: 'Create a group', + no_group_create_group: 'Create group', + no_group_group_name: 'Group name:', + no_group_max_members: 'Max members:', + no_group_description: 'Description:', + no_group_close: 'Close', + no_group_join_group: 'Join group', + no_group_join_blank: 'Join', + login_message: 'Login successful', + register_message: 'You registered successfully', + game_single_player: 'Single Player', + game_multiplayer: 'Multiplayer', profile_uuid: 'User ID:', profile_name: 'Name:', profile_created_at: 'Account creation date:', + profile_performance_statistics: 'Performance Statistics', profile_points: 'Total points:', profile_nwins: 'Number of victories:', profile_n_correct_answers: 'Correct answers:', profile_n_wrong_answers: 'Wrong answers:', - profile_last_game_questions: 'Last game questions', - profile_last_game_questions_question: 'Question:', - profile_last_game_questions_warning: 'You have not played any games yet', - profile_last_game_questions_correct_answer: 'Correct answer:', - profile_last_game_questions_incorrect_answer_1: 'Incorrect answer 1:', - profile_last_game_questions_incorrect_answer_2: 'Incorrect answer 2:', - profile_last_game_questions_incorrect_answer_3: 'Incorrect answer 3:', - group_table_member: 'Member', - group_table_leader: 'Leader', - group_table_username: 'Username', - group_table_role: 'Role', - group_table_score: 'Score', - create_group_group_name: 'Group name:', - create_group_public_group: 'Public:', - create_group_max_members: 'Max members:', - create_group_description: 'Description:', - create_group_button: 'Create group', - not_part_of_group: 'You are not part of a group...', - join_group_button: 'Join a group', - join_this_group_button: 'Join', - close_button: 'Close', + profile_last_game_id: 'Last game ID: ', + profile_questions: 'Questions:', + profile_last_game_questions_question_blank: 'Question ', + profile_last_game_questions: 'Last Game', + profile_last_game_questions_question: 'Question: ', + profile_last_game_questions_correct_answer: 'Correct answer: ', + profile_last_game_questions_incorrect_answer_1: 'Incorrect answer 1: ', + profile_last_game_questions_incorrect_answer_2: 'Incorrect answer 2: ', + profile_last_game_questions_incorrect_answer_3: 'Incorrect answer 3: ' } }, es: { @@ -57,39 +108,90 @@ i18n login: 'Iniciar sesión', return: 'Volver', register: 'Registrarse', + label_username: 'Nombre de usuario', + label_password: 'Contraseña', login_google: 'Iniciar sesión con Google', nav_game: 'Juego', nav_groups: 'Grupos', nav_scoreboard: 'Puntuación', nav_profile: 'Perfil', + nav_logout: 'Cerrar sesión', + g_login_button_login_failed: 'Fallo de Inicio de Sesión', + lobby_multiplayer_title: 'Lobby - Multijugador', + lobby_multiplayer_party_code: 'Código de sala: ', + lobby_multiplayer_admin: 'Administrador', + lobby_multiplayer_player: 'Jugador', + lobby_multiplayer_points: 'Puntos: ', + lobby_multiplayer_exit: 'Salir', + lobby_multiplayer_start_game: 'Comenzar partida', + lobby_multiplayer_loading_questions: 'Cargando preguntas...', + menu_multiplayer_create_or_join: 'Crea un sala o únete a una', + menu_multiplayer_create: 'Crear Sala', + menu_multiplayer_join: 'Unirse a Sala', + questions_multiplayer_question: 'Pregunta ', + questions_multiplayer_you_answered: 'Respondiste ', + questions_multiplayer_out_of: ' de ', + questions_multiplayer_questions_correctly: ' preguntas correctamente.', + questions_multiplayer_you_earned: 'Ganaste ', + questions_multiplayer_points: ' puntos.', + questions_multiplayer_waiting_players: 'Esperando a que el resto de los jugadores terminen...', + game_single_player_error: 'Error', + lobby_single_player_title: 'Lobby - Un Solo Jugador', + lobby_single_player_total_points: 'Total de puntos: ', + lobby_single_player_delete: 'Borrar', + lobby_single_player_add_bot_player: 'Añadir Bot Jugador', + lobby_single_player_start_game: 'Comenzar Partida', + lobby_single_player_loading_questions: 'Cargando Preguntas...', + playing_single_player_you_answered: 'Respondiste ', + playing_single_player_out_of: ' de ', + playing_single_player_questions_correctly: ' preguntas correctamente.', + playing_single_player_you_earned: 'Ganaste ', + playing_single_player_points: ' puntos.', + playing_single_player_next: 'Siguiente', + scoreboard_game_game_scoreboard: 'Marcador del Juego', + scoreboard_game_position: 'Posición', + scoreboard_game_username: 'Nombre de Usuario', + scoreboard_game_points: 'Puntos', + game_layout_game: 'Juego', + game_layout_groups: 'Grupos', + game_layout_scoreboard: 'Marcador del Juego', + group_table_points: ' puntos', + group_table_members: ' miembros', + group_table_leave: 'Salir', + group_table_username: 'Nombre de Usuario', + group_table_role: 'Rol', + group_table_score: 'Puntuación', + no_group_not_part: 'Aún no eres parte de un grupo...', + no_group_join: 'Unirse a un grupo', + no_group_create: 'Crear un grupo', + no_group_create_group: 'Crear grupo', + no_group_group_name: 'Nombre del grupo:', + no_group_max_members: 'Máximo de miembros:', + no_group_description: 'Descripción:', + no_group_close: 'Cerrar', + no_group_join_group: 'Unirse a grupo', + no_group_join_blank: 'Unirse', + login_message: 'Inicio de sesión exitoso', + register_message: 'Registro exitoso', + game_single_player: 'Un Solo Jugador', + game_multiplayer: 'Multijugador', profile_uuid: 'ID del jugador:', profile_name: 'Nombre:', profile_created_at: 'Fecha de creación de cuenta:', + profile_performance_statistics: 'Estadísticas de Desempeño', profile_points: 'Puntuación total:', profile_nwins: 'Número de victorias:', profile_n_correct_answers: 'Respuestas correctas:', profile_n_wrong_answers: 'Respuestas incorrectas:', - profile_last_game_questions: 'Preguntas de la última partida', - profile_last_game_questions_warning: 'Todavía no has jugado ninguna partida', - profile_last_game_questions_question: 'Pregunta:', - profile_last_game_questions_correct_answer: 'Respuesta correcta:', - profile_last_game_questions_incorrect_answer_1: 'Respuesta incorrecta 1:', - profile_last_game_questions_incorrect_answer_2: 'Respuesta incorrecta 2:', - profile_last_game_questions_incorrect_answer_3: 'Respuesta incorrecta 3:', - group_table_member: 'Miembro', - group_table_leader: 'Líder', - group_table_username: 'Nombre de usuario', - group_table_role: 'Rol', - group_table_score: 'Puntuación', - create_group_group_name: 'Nombre del grupo:', - create_group_public_group: 'Público:', - create_group_max_members: 'Número máximo de miembros:', - create_group_description: 'Descripción:', - create_group_button: 'Crear grupo', - not_part_of_group: 'No eres parte de un grupo...', - join_group_button: 'Unirse a un grupo', - join_this_group_button: 'Unirse', - close_button: 'Cerar', + profile_last_game_id: 'ID de la última partida: ', + profile_questions: 'Preguntas:', + profile_last_game_questions_question_blank: 'Pregunta ', + profile_last_game_questions: 'Última Partida', + profile_last_game_questions_question: 'Pregunta: ', + profile_last_game_questions_correct_answer: 'Respuesta correcta: ', + profile_last_game_questions_incorrect_answer_1: 'Respuesta incorrecta 1: ', + profile_last_game_questions_incorrect_answer_2: 'Respuesta incorrecta 2: ', + profile_last_game_questions_incorrect_answer_3: 'Respuesta incorrecta 3: ' } }, diff --git a/webapp/src/pages/game/GamePage.css b/webapp/src/pages/game/game-page.scss similarity index 92% rename from webapp/src/pages/game/GamePage.css rename to webapp/src/pages/game/game-page.scss index 45d63edf..2696ba2f 100644 --- a/webapp/src/pages/game/GamePage.css +++ b/webapp/src/pages/game/game-page.scss @@ -7,10 +7,10 @@ } .game-page-button { - width: 220px; + width: 290px; font-size: 1.5em; padding: 15px 30px; - margin: 10px; + margin-bottom: 20px; text-decoration: none; background-color: #007bff; color: #ffffff; diff --git a/webapp/src/pages/game/index.tsx b/webapp/src/pages/game/index.tsx index 4f2fcb77..c3520e4d 100644 --- a/webapp/src/pages/game/index.tsx +++ b/webapp/src/pages/game/index.tsx @@ -1,15 +1,19 @@ import React from "react"; import { Link } from "react-router-dom"; -import "./GamePage.css"; +import "./game-page.scss"; +import { useTranslation } from 'react-i18next'; export const GamePage: React.FC<{}> = () => { + + const { t } = useTranslation(); + return (
    ); diff --git a/webapp/src/pages/groups/groups-page.scss b/webapp/src/pages/groups/groups-page.scss index 34bce45e..419a3ade 100644 --- a/webapp/src/pages/groups/groups-page.scss +++ b/webapp/src/pages/groups/groups-page.scss @@ -1,51 +1,7 @@ -body.active-modal { - overflow-y: hidden; -} - -.btn-modal { - padding: 10px 20px; - display: block; - margin: 100px auto 0; - font-size: 18px; -} - -.modal, .overlay { - width: 100vw; - height: 100vh; - top: 0; - left: 0; - right: 0; - bottom: 0; - position: fixed; -} - -.overlay { - background: rgba(49,49,49,0.8); -} -.modal-content { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - line-height: 1.4; - background: #f1f1f1; - padding: 14px 28px; - border-radius: 3px; - max-width: 1000px; - min-width: 500px; -} - -.close-modal { - position: absolute; - top: 10px; - right: 10px; - padding: 5px 7px; -} - .groups-container{ display: flex; flex-direction: column; align-items: center; justify-content: center; - height: 50vh; + height: 100vh; } \ No newline at end of file diff --git a/webapp/src/pages/groups/index.tsx b/webapp/src/pages/groups/index.tsx index 5e256860..163d8c19 100644 --- a/webapp/src/pages/groups/index.tsx +++ b/webapp/src/pages/groups/index.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Button, Container } from "@mui/material"; +import { Container } from "@mui/material"; import axios from 'axios'; import "./groups-page.scss"; import NoGroup from 'src/components/group/NoGroup'; @@ -21,7 +21,7 @@ export const GroupsPage: React.FC<{}> = () => { for(var group of groups.data){ for(var member of group.members){ const uuid = JSON.stringify(member).replace("\"", "").replace("\"", ""); - if(userUuid == uuid){ + if(userUuid === uuid){ inGroup = true; groupUUID = JSON.stringify(group.uuid).replace("\"", "").replace("\"", ""); } @@ -38,7 +38,7 @@ export const GroupsPage: React.FC<{}> = () => { }); return( - + { signedUp? ( diff --git a/webapp/src/pages/userProfile/index.tsx b/webapp/src/pages/userProfile/index.tsx index 756855da..4b233ff5 100644 --- a/webapp/src/pages/userProfile/index.tsx +++ b/webapp/src/pages/userProfile/index.tsx @@ -1,83 +1,176 @@ -import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Container, Grid, Paper, Typography } from '@mui/material'; -import axios from 'axios'; import './profile-page.scss'; +import { useEffect, useState } from 'react'; +import axios from 'axios'; const ProfilePage = () => { - const { t } = useTranslation(); - const user = JSON.stringify(localStorage.getItem("username")).replace("\"", "").replace("\"", ""); - const email = JSON.stringify(localStorage.getItem("email")).replace("\"", "").replace("\"", ""); - const createdAt = localStorage.getItem("createdAt"); - const score = localStorage.getItem("totalScore"); - const nwins = localStorage.getItem("nwins"); - const nCorrectAnswers = localStorage.getItem("nCorrectAnswers"); - const nWrongAnswers = localStorage.getItem("nWrongAnswers"); - const [gameInfo, setGameInfo] = useState(null); - const uuid = localStorage.getItem('uuid'); - - useEffect(() => { - const fetchGameInfo = async () => { - try { - const response = await axios.get(`http://localhost:8000/getStats/${uuid}`); - setGameInfo(response.data); - } catch (error) { - console.error('Error fetching game information:', error); - } - }; - fetchGameInfo(); - }, [uuid]); + const { t } = useTranslation(); + const apiEndpoint = 'http://localhost:8000'; + const uuid = localStorage.getItem('uuid'); + const [profileInfo, setProfileInfo] = useState(null); - const formatDate = (date: any) => { - if (!date) return ""; - const options = { year: 'numeric', month: 'long', day: 'numeric' }; - return date.toLocaleDateString(undefined, options); + useEffect(() => { + const fetchData = async () => { + try { + const response = await axios.get(`${apiEndpoint}/getStats/${uuid}`); + setProfileInfo(response.data); + } catch (error) { + console.error('Error fetching data:', error); + } }; + fetchData(); + }, [apiEndpoint, uuid]); - return( - - Profile - - - - Personal Information + const formatDate = (dateString: string) => { + if (!dateString) return ""; + const date = new Date(dateString); + const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }; + return date.toLocaleDateString('en-US', options); + }; + + return ( + + + + {profileInfo && ( + {profileInfo.userStats.username} + )} + + + + + + + Personal Information + + {profileInfo && (
      -
    • {t('profile_uuid')} { uuid }
    • -
    • {t('profile_name')} { user }
    • -
    • {t('profile_created_at')} {formatDate(createdAt) }
    • +
    • + + {t('profile_uuid')} {profileInfo.userStats.uuid} + +
    • +
    • + + {t('profile_name')} {profileInfo.userStats.username} + +
    • +
    • + + {t('profile_created_at')} {formatDate(profileInfo.userStats.createdAt)} + +
    -
    -
    - - - Performance Statistics + )} + + + + + + {t('profile_performance_statistics')} + + {profileInfo && (
      -
    • {t('profile_points')} { JSON.stringify(Number(score)) }
    • -
    • {t('profile_nwins')} { JSON.stringify(Number(nwins)) }
    • -
    • {t('profile_n_correct_answers')} { JSON.stringify(Number(nCorrectAnswers)) }
    • -
    • {t('profile_n_wrong_answers')} { JSON.stringify(Number(nWrongAnswers)) }
    • +
    • + + {t('profile_points')} {Number(profileInfo.userStats.totalScore)} + +
    • +
    • + + {t('profile_nwins')} {Number(profileInfo.userStats.nWins)} + +
    • +
    • + + {t('profile_n_correct_answers')} {Number(profileInfo.userStats.nCorrectAnswers)} + +
    • +
    • + + {t('profile_n_wrong_answers')} {Number(profileInfo.userStats.nWrongAnswers)} + +
    -
    -
    - - - {t('profile_last_game_questions')} - {gameInfo ? ( -
      + )} + + + + + + {t('profile_last_game_questions')} + + {profileInfo && ( +
      + + {t('profile_last_game_id')}{profileInfo.userStats.lastGameId} + + + {t('profile_questions')} + +
        + {profileInfo.lastGame.map((question, index) => ( +
      • + + {t('profile_last_game_questions_question_blank')}{index} + +
          +
        • + + {t('profile_last_game_questions_question')}{question[0].question} + +
        • +
        • + + {t('profile_last_game_questions_correct_answer')}{question[0].correctAnswer} + +
        • +
        • + + {t('profile_last_game_questions_incorrect_answer_1')}{question[0].incorrectAnswer1} + +
        • +
        • + + {t('profile_last_game_questions_incorrect_answer_2')}{question[0].incorrectAnswer2} + +
        • +
        • + + {t('profile_last_game_questions_incorrect_answer_3')}{question[0].incorrectAnswer3} + +
        • +
        +
      • + ))}
      - ) : ( - {t('profile_last_game_questions_warning')} - )} - - - - - Additional Information - - +
      + )} +
      - - ) -} + + + ); +}; export default ProfilePage; \ No newline at end of file diff --git a/webapp/src/pages/userProfile/profile-page.scss b/webapp/src/pages/userProfile/profile-page.scss index d27f51a9..805695a8 100644 --- a/webapp/src/pages/userProfile/profile-page.scss +++ b/webapp/src/pages/userProfile/profile-page.scss @@ -6,6 +6,15 @@ font-size: 25px; } +.profile-picture { + max-width: none; + width: 200px; + height: 200px; + border-radius: 50%; + border: 4px rgb(25, 118, 210) solid; + margin-bottom: 30px; +} + .field { font-size: 18px; font-style: italic;
    Game Scoreboard{t('scoreboard_game_game_scoreboard')}
    - Position - Username - Points{t('scoreboard_game_position')}{t('scoreboard_game_username')}{t('scoreboard_game_points')}