diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac542fe1..bfb8a8a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -231,7 +231,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} registry: ghcr.io - workdir: prometheus + workdir: gatewayservice/monitoring/prometheus docker-push-grafana: name: Push Grafana Docker Image to GitHub Packages runs-on: ubuntu-latest @@ -248,12 +248,13 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} registry: ghcr.io - workdir: grafana + workdir: gatewayservice/monitoring/grafana deploy: name: Deploy over SSH runs-on: ubuntu-latest needs: [docker-push-userservice,docker-push-authservice,docker-push-gatewayservice,docker-push-webapp, - docker-push-questiongenerator,docker-push-gamehistoryservice,docker-push-perfilservice,docker-push-allquestionservice,docker-push-alluserservice] + docker-push-questiongenerator,docker-push-gamehistoryservice,docker-push-perfilservice,docker-push-allquestionservice,docker-push-alluserservice, + docker-push-prometheus, docker-push-grafana] steps: - name: Deploy over SSH uses: fifsky/ssh-action@master diff --git a/docs/src/12_test_report.adoc b/docs/src/12_test_report.adoc index 2c22eaa0..1b1e047d 100644 --- a/docs/src/12_test_report.adoc +++ b/docs/src/12_test_report.adoc @@ -4,6 +4,27 @@ ifndef::imagesdir[:imagesdir: ../images] == Informe de pruebas === Pruebas de cobertura +Las pruebas de cobertura prueban la funcionalidad de la aplicación creando, al mismo tiempo, una métrica que indica cuanto del código creado está cubierto por dichas pruebas. +Las pruebas se han realizado en todos los servicios de la aplicación, a fin de comprobar que la funcionalidad de estos es la esperada. + +Para las pruebas de cobertura se ha utilizado, principalmente, las liberías: + +* testing-library/react +* supertest +* axios +* sinon + +A continuación, se explica para que se ha utilizado cada una de dichas librerías: +[options="header",cols="1,1,1"] +|=== +|Librería|Contenido|Uso +| testing-library/react | Contiene todas las funciones necesarias para hacer pruebas con los componentes de REACT como: render, fireEvent, act o waitFor | Para los tests de los componentes de REACT que se encuentran en webapp +| supertest | La función request que se utiliza para realizar peticiones | Para todas aquellas pruebas que requieran comprobar una petición a una URL, incluyendo el envío de parámetros y la comprobación de la respuesta +| axios | Todas las funciones necesarias para hacer Mocks | Para todos los tests que requerían del uso de mocks. Por ejemplo, para probar el juego hemos mockeado las llamadas al generador de preguntas, para no depender de este +| sinon | Contiene la función stub que permite sobresscribir los métodos HTTP al realizar peticiones | Principalmente, para los tests en los que había que simular un cierto valor de respuesta o un error en la petición sin necesidad de causar dicho error al hacer la petición +|=== + +Además de todas estas librerías externas, utilizamos, para practicamente todas las pruebas, el framework jest, muy utilizado para hacer las pruebas de proyectos que utilizan REACT, como es nuestro caso. Este framework es el que nos permite definir los casos de prueba y controlar las peticiones que realizamos utilizando, por ejemplo, la función spyOn que nos permite espionar una función o petición. === Pruebas de usabilidad diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 78170d57..9cbe75da 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -21,7 +21,7 @@ const allUsersServiceUrl = process.env.ALLUSERS_SERVICE_URL || 'http://localhost const allQuestionsServiceUrl = process.env.ALLQUESTIONS_SERVICE_URL || 'http://localhost:8007'; const corsOptions = { - origin: originEndpoint, + origin: `${originEndpoint}`, methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'] }; diff --git a/gatewayservice/monitoring/grafana/Dockerfile b/gatewayservice/monitoring/grafana/Dockerfile new file mode 100644 index 00000000..af00b6db --- /dev/null +++ b/gatewayservice/monitoring/grafana/Dockerfile @@ -0,0 +1,13 @@ +# Usa la imagen base de Grafana desde Docker Hub +FROM grafana/grafana + +USER root + +RUN addgroup -S nonroot \ + && adduser -S nonroot -G nonroot + +RUN chown -R nonroot:nonroot ./ + +COPY provisioning ./provisioning + +USER nonroot \ No newline at end of file diff --git a/gatewayservice/monitoring/grafana/provisioning/dashboards/example-service-dashboard.json b/gatewayservice/monitoring/grafana/provisioning/dashboards/dashboard.json similarity index 100% rename from gatewayservice/monitoring/grafana/provisioning/dashboards/example-service-dashboard.json rename to gatewayservice/monitoring/grafana/provisioning/dashboards/dashboard.json diff --git a/gatewayservice/monitoring/prometheus/Dockerfile b/gatewayservice/monitoring/prometheus/Dockerfile new file mode 100644 index 00000000..811b8da8 --- /dev/null +++ b/gatewayservice/monitoring/prometheus/Dockerfile @@ -0,0 +1,13 @@ +# Usa la imagen base de Prometheus desde Docker Hub +FROM prom/prometheus + +USER root + +RUN addgroup -S nonroot \ + && adduser -S nonroot -G nonroot + +RUN chown -R nonroot:nonroot ./ + +COPY prometheus.yml ./ + +USER nonroot \ No newline at end of file diff --git a/gatewayservice/openapi.yaml b/gatewayservice/openapi.yaml index 8656f624..9ff2939f 100644 --- a/gatewayservice/openapi.yaml +++ b/gatewayservice/openapi.yaml @@ -4,9 +4,9 @@ info: description: Gateway OpenAPI specification. version: 0.2.0 servers: - - url: http://${{ secrets.DEPLOY_HOST }}:8000 + - url: http://localhost:8000 description: Development server - - url: http://${{ secrets.DEPLOY_HOST }}:8000 + - url: http://${{secrets.DEPLOY_HOST}}:8000 description: Production server paths: /adduser: @@ -23,11 +23,15 @@ paths: username: type: string description: User ID. - example: student + example: Pepito + email: + type: string + description: User email. + example: pepito@gmail.com password: type: string description: User password. - example: pass + example: passPepito responses: '200': description: User added successfully. @@ -95,11 +99,11 @@ paths: username: type: string description: User ID. - example: student + example: Pepito password: type: string description: User password. - example: pass + example: passPepito responses: '200': description: Login successful. Returns user token, username, and creation date. @@ -115,7 +119,7 @@ paths: username: type: string description: Username. - example: student + example: Pepito createdAt: type: string description: Creation date. @@ -146,6 +150,28 @@ paths: get: summary: Generate a question. operationId: generateQuestion + parameters: + - name: user + in: query + description: User of the game. + example: testuser + required: true + schema: + type: string + - name: thematic + in: query + description: The thematic of the questions. + example: Geografia + required: true + schema: + type: string + - name: language + in: query + description: The language of the questions. + example: es + required: true + schema: + type: string responses: '200': description: Generation successful. Returns the question, the options, the correct option, the image (if neccessary) and the question id @@ -197,14 +223,14 @@ paths: summary: Update a question. operationId: updateQuestion parameters: - time: + - name: time in: query description: Time of the question. example: 10 required: true schema: type: string - correct: + - name: correct in: query description: If the question was answered correctly. example: true @@ -303,6 +329,17 @@ paths: post: summary: Configure the game. operationId: configureGame + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + maxQuestions: + type: integer + description: Number of questions. + example: 10 responses: '200': description: Succesful configure the game number of questions @@ -313,7 +350,7 @@ paths: properties: maxQuestions: type: int - description: The new number of questions of the question. + description: The new number of questions of the game. example: 10 '400': description: Error when configuring the game @@ -342,7 +379,7 @@ paths: summary: Charge the game history. operationId: gamehistory parameters: - username: + - name: username in: query description: User which game history we want to recover. example: Pepito @@ -352,6 +389,39 @@ paths: responses: '200': description: Succesful charge the game history + content: + application/json: + schema: + type: object + properties: + userId: + type: string + description: User ID. + example: Pepito + totalGamesPlayed: + type: integer + description: Number of games played by the user. + example: 10 + totalQuestionsAnswered: + type: integer + description: Number of questions answered by the user. + example: 30 + totalRightQuestions: + type: integer + description: Number of questions answered correctly by the user. + example: 15 + totalIncorrectQuestions: + type: integer + description: Number of questions answered incorrectly by the user. + example: 15 + ratio: + type: string + description: Ratio between correct and incorrect questions. + example: 50% + totalTime: + type: string + description: Time the user spend in the game. + example: 150s '400': description: Error when charging the game history '500': @@ -370,7 +440,7 @@ paths: summary: Get an specific user operationId: getUser parameters: - username: + - name: username in: query description: The name of the user we want to get. example: Pepito @@ -381,22 +451,22 @@ paths: '200': description: Succesful get the user content: - application/json: - schema: - type: object - properties: - username: - type: string - description: The name of the user. - example: Pepito - email: - type: string - description: The email of the user. - example: pepito@gmail.com - creado: - type: string - description: The date where the user account was created. - example: 01/01/2024 + application/json: + schema: + type: object + properties: + user- name: + type: string + description: The name of the user. + example: Pepito + email: + type: string + description: The email of the user. + example: pepito@gmail.com + creado: + type: string + description: The date where the user account was created. + example: 01/01/2024 '400': description: Error when getting the user '500': @@ -418,14 +488,14 @@ paths: '200': description: Succesful get all the users content: - application/json: - schema: - type: object - properties: - users: - type: array - description: The information about the users (username, email, creation date). - example: [Pepito, pepito@gmail.com, 01/01/2024] + application/json: + schema: + type: object + properties: + users: + type: array + description: The information about the users (username, email, creation date). + example: [Pepito, pepito@gmail.com, 01/01/2024] '400': description: Error when getting the users '500': @@ -447,14 +517,14 @@ paths: '200': description: Succesful get all the questions content: - application/json: - schema: - type: object - properties: - users: - type: array - description: The information about the questions (question an correct answer). - example: [¿Cual es la capital de España?,Madrid] + application/json: + schema: + type: object + properties: + users: + type: array + description: The information about the questions (question an correct answer). + example: [¿Cual es la capital de España?,Madrid] '400': description: Error when getting the questions '500': @@ -497,12 +567,56 @@ paths: type: string description: Error information. example: Internal Server Error + /ranking: + get: + summary: Charge the ranking. + operationId: topUsers + parameters: + - name: sortBy + in: query + description: What we want to use to sort the ranking. + example: ratio + required: true + schema: + type: string + - name: userLimit + in: query + description: The number of users we want to recover. + example: 5 + required: true + schema: + type: integer + responses: + '200': + description: Succesful charge the ranking + content: + application/json: + schema: + type: object + properties: + users: + type: array + description: The ranking of users. + example: [Pepito, Fulanito, Menganito, Juan, Laura] + '400': + description: Error when charging the ranking + '500': + description: Internal server error. + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: Error information. + example: Internal Server Error /endgamestats: get: summary: Charge the end game statistics. operationId: endgamestats parameters: - username: + - name: username in: query description: User which game statistics we want to charge. example: Pepito @@ -513,30 +627,30 @@ paths: '200': description: Succesful charge the end game statitics content: - application/json: - schema: - type: object - properties: - totalRightQuestions: - type: integer - description: The number of questions that the user has answered correctly. - example: 5 - totalIncorrectQuestions: - type: integer - description: The number of questions that the user has answered incorrectly. - example: 0 - ratio: - type: string - description: The ratio between the correct and incorrect questions. - example: 100% - totalTime: - type: string - description: The time the user has spend at the game. - example: 20s - endgameImageWithRatio: - type: string - description: The end game ratio of questions - example: 100% + application/json: + schema: + type: object + properties: + totalRightQuestions: + type: integer + description: The number of questions that the user has answered correctly. + example: 5 + totalIncorrectQuestions: + type: integer + description: The number of questions that the user has answered incorrectly. + example: 0 + ratio: + type: string + description: The ratio between the correct and incorrect questions. + example: 100% + totalTime: + type: string + description: The time the user has spend at the game. + example: 20s + endgameImageWithRatio: + type: string + description: The end game ratio of questions + example: 100% '400': description: Error when charging the end game statistics '500': @@ -555,7 +669,7 @@ paths: summary: Restart the game. operationId: endgamestats parameters: - username: + - name: username in: query description: User which game statistics we want to charge. example: Pepito @@ -566,18 +680,18 @@ paths: '200': description: Succesful charge the end game statistics content: - application/json: - schema: - type: object - properties: - message: - type: string - description: A message indicating the number of questions has been modified. - example: Número de preguntas actualizado - numberOfQuestions: - type: integer - description: The number of questions that has been modified. - example: 5 + application/json: + schema: + type: object + properties: + message: + type: string + description: A message indicating the number of questions has been modified. + example: Número de preguntas actualizado + numberOfQuestions: + type: integer + description: The number of questions that has been modified. + example: 5 '400': description: Error when charging the end game statistics '500': diff --git a/questiongenerator/question.test.js b/questiongenerator/question.test.js index a587c1f4..0dc2ad9c 100644 --- a/questiongenerator/question.test.js +++ b/questiongenerator/question.test.js @@ -47,7 +47,7 @@ describe('Question Generator test', () => { expect(response.status).toBe(200); expect(response.body).toHaveProperty('responseQuestion', 'responseOptions', 'responseCorrectOption', 'question_Id', 'responseImage'); - }, 10000); + }, 15000); it('Should manager errors when calling /generateQuestion', async () => { await simulateError('get', '/generateQuestion', 'Error al obtener datos', { error: "Error al obtener datos RangeError [ERR_OUT_OF_RANGE]: The value of \"max\" is out of range. It must be greater than the value of \"min\" (0). Received 0" }); diff --git a/webapp/public/brain-icon2.ico b/webapp/public/brain-icon2.ico new file mode 100644 index 00000000..3c569191 Binary files /dev/null and b/webapp/public/brain-icon2.ico differ diff --git a/webapp/public/brain-icon2.png b/webapp/public/brain-icon2.png new file mode 100644 index 00000000..3c569191 Binary files /dev/null and b/webapp/public/brain-icon2.png differ diff --git a/webapp/public/favicon.ico b/webapp/public/favicon.ico deleted file mode 100644 index 390d4dcf..00000000 Binary files a/webapp/public/favicon.ico and /dev/null differ diff --git a/webapp/public/index.html b/webapp/public/index.html index 799d79de..1dd0bcd6 100644 --- a/webapp/public/index.html +++ b/webapp/public/index.html @@ -9,7 +9,7 @@ name="description" content="Web site created using create-react-app" /> - + - WIQ2C + BrainWIQ diff --git a/webapp/public/logo192.png b/webapp/public/logo192.png deleted file mode 100644 index 390d4dcf..00000000 Binary files a/webapp/public/logo192.png and /dev/null differ diff --git a/webapp/public/manifest.json b/webapp/public/manifest.json index 080d6c77..671dd3d3 100644 --- a/webapp/public/manifest.json +++ b/webapp/public/manifest.json @@ -8,12 +8,12 @@ "type": "image/x-icon" }, { - "src": "logo192.png", + "src": "brain-icon2.png", "type": "image/png", "sizes": "192x192" }, { - "src": "logo512.png", + "src": "brain-icon2.png", "type": "image/png", "sizes": "512x512" } diff --git a/webapp/src/App.css b/webapp/src/App.css index 20f30f96..3305bb1b 100644 --- a/webapp/src/App.css +++ b/webapp/src/App.css @@ -1,4 +1,5 @@ body { - background-color: #F3D3FA; -} - + background-image: url('./components/images/fondo.png'); + background-repeat: no-repeat; + background-size: cover; + } \ No newline at end of file diff --git a/webapp/src/App.js b/webapp/src/App.js index fdcfb109..bfb1701a 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -2,22 +2,14 @@ import React, { useState } from 'react'; import AddUser from './components/AddUser'; import Login from './components/Login'; import CssBaseline from '@mui/material/CssBaseline'; -import Container from '@mui/material/Container'; import Typography from '@mui/material/Typography'; import Link from '@mui/material/Link'; +import Box from '@mui/material/Box'; import './App.css'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; import { useTranslation } from 'react-i18next'; +import { CustomContainer } from './CustomContainer'; -const theme = createTheme({ - palette: { - background: { - default: '#F3D3FA', - }, - }, -}); - function App() { const [t] = useTranslation("global"); @@ -29,31 +21,23 @@ function App() { }; return ( - - + + {showLogin ? : } {showLogin ? ( - + {t("enlaceLogin")} ) : ( - + {t("enlaceRegistro")} )} - - + + ); } diff --git a/webapp/src/CustomContainer.js b/webapp/src/CustomContainer.js new file mode 100644 index 00000000..549a7492 --- /dev/null +++ b/webapp/src/CustomContainer.js @@ -0,0 +1,22 @@ +import Container from '@mui/material/Container';; + +export function CustomContainer({ children, ...props }) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/webapp/src/components/AddUser.js b/webapp/src/components/AddUser.js index c2e43e6a..5e5395db 100644 --- a/webapp/src/components/AddUser.js +++ b/webapp/src/components/AddUser.js @@ -1,9 +1,10 @@ // src/components/AddUser.js import React, { useState } from 'react'; import axios from 'axios'; -import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; +import {Typography, TextField, Button, Snackbar } from '@mui/material'; import '../App.css'; import { useTranslation } from 'react-i18next'; +import { CustomContainer } from '../CustomContainer'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -36,7 +37,7 @@ const AddUser = () => { }; return ( - { alignItems: 'center', }}>
- + {t("registro")} { label={t("usuario")} value={username} onChange={(e) => setUsername(e.target.value)} - sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} + sx={{ + marginBottom: 4, + backgroundColor: '#FFFFFF', + '& .MuiOutlinedInput-root': { + '&.Mui-focused fieldset': { + borderColor: '#EE6D72', + }, + }, + '& .MuiInputLabel-root': { + '&.Mui-focused': { + color: '#EE6D72', + }, + }, + }} /> { label={t("email")} value={email} onChange={(e) => setEmail(e.target.value)} - sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} + sx={{ + marginBottom: 4, + backgroundColor: '#FFFFFF', + '& .MuiOutlinedInput-root': { + '&.Mui-focused fieldset': { + borderColor: '#EE6D72', + }, + }, + '& .MuiInputLabel-root': { + '&.Mui-focused': { + color: '#EE6D72', + }, + }, + }} /> { type="password" value={password} onChange={(e) => setPassword(e.target.value)} - sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} + sx={{ + marginBottom: 4, + backgroundColor: '#FFFFFF', + '& .MuiOutlinedInput-root': { + '&.Mui-focused fieldset': { + borderColor: '#EE6D72', + }, + }, + '& .MuiInputLabel-root': { + '&.Mui-focused': { + color: '#EE6D72', + }, + }, + }} /> - { message={`Error: ${error}`}/> )}
-
+ ); }; diff --git a/webapp/src/components/AllQuestions.js b/webapp/src/components/AllQuestions.js index 5a749f05..b4a11bce 100644 --- a/webapp/src/components/AllQuestions.js +++ b/webapp/src/components/AllQuestions.js @@ -1,8 +1,9 @@ import axios from 'axios'; import React, { useState, useEffect, useCallback } from 'react'; -import { Container, Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; +import {Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; import { useTranslation } from 'react-i18next'; import '../App.css'; +import { CustomContainer } from '../CustomContainer'; const AllQuestions = () => { @@ -29,17 +30,9 @@ const AllQuestions = () => { return ( - - + + {t("textoAllQuestions")} @@ -65,7 +58,7 @@ const AllQuestions = () => { setError('')} message={`Error: ${error}`} /> )} - + ); }; diff --git a/webapp/src/components/AllUsers.js b/webapp/src/components/AllUsers.js index 5163e13b..da45adc0 100644 --- a/webapp/src/components/AllUsers.js +++ b/webapp/src/components/AllUsers.js @@ -1,8 +1,9 @@ import axios from 'axios'; import React, { useState, useEffect, useCallback } from 'react'; -import { Container, Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; +import {Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; import { useTranslation } from 'react-i18next'; import '../App.css'; +import { CustomContainer } from '../CustomContainer'; const AllUsers = () => { @@ -32,17 +33,9 @@ const AllUsers = () => { return ( - - + + {t("textoAllUsers")} @@ -70,7 +63,7 @@ const AllUsers = () => { setError('')} message={`Error: ${error}`} /> )} - + ); }; diff --git a/webapp/src/components/EndGame.js b/webapp/src/components/EndGame.js index 0f46c401..f4587916 100644 --- a/webapp/src/components/EndGame.js +++ b/webapp/src/components/EndGame.js @@ -1,10 +1,13 @@ import React, { useState, useEffect, useCallback } from 'react'; import axios from 'axios'; -import { Container, Box, Typography, Grid, Button, Snackbar} from '@mui/material'; +import { Box, Typography, Grid, Button, Snackbar} from '@mui/material'; import { useUser } from './UserContext'; import { useNavigate } from 'react-router-dom'; import HomeIcon from '@mui/icons-material/Home'; import '../App.css'; +import { CustomContainer } from '../CustomContainer'; +import { useTranslation } from 'react-i18next'; + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -15,6 +18,9 @@ const EndGame = () => { const [endGame, setEndGame] = useState(''); const [error, setError] = useState(''); + const [t] = useTranslation("global"); + + const getEndGame = useCallback(async () => { try { const response = await axios.get(`${apiEndpoint}/endgamestats`,{ @@ -39,55 +45,50 @@ const EndGame = () => { const selectImage = () => { if (endGame.endgameImageWithRatio >= 0 && endGame.endgameImageWithRatio < 25) { - return require('./images/ronnie-muletas.png'); + return require('./images/brain-angry.png'); } else if (endGame.endgameImageWithRatio >= 25 && endGame.endgameImageWithRatio < 50) { - return require('./images/ronnie-comiendo.png'); + return require('./images/brain-strong.png'); } else { - return require('./images/ronnie.gif'); + return require('./images/brain-strong2.png'); } }; return ( - - - End Game + + + End Game - Estadísticas de la última partida + {t("textoEstadisticas")} - Preguntas correctas: {endGame.totalRightQuestions} + {t("correctas")} {endGame.totalRightQuestions} - Preguntas incorrectas: {endGame.totalIncorrectQuestions} + {t("incorrectas")} {endGame.totalIncorrectQuestions} - Ratio de aciertos: {endGame.ratio} + {t("ratio")} {endGame.ratio} - Tiempo total: {endGame.totalTime} + {t("tiempoTotal")} {endGame.totalTime} - @@ -96,7 +97,7 @@ const EndGame = () => { setError('')} message={`Error: ${error}`} /> )} - + ); }; diff --git a/webapp/src/components/Game.js b/webapp/src/components/Game.js index 5a013e62..5e5a68c5 100644 --- a/webapp/src/components/Game.js +++ b/webapp/src/components/Game.js @@ -6,6 +6,8 @@ import { useNavigate, useLocation } from 'react-router-dom'; import { useUser } from './UserContext'; import '../App.css'; import { useTranslation } from 'react-i18next'; +import { CustomContainer } from '../CustomContainer'; + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -141,17 +143,7 @@ const Game = () => { return ( - + {question && ( <> @@ -168,6 +160,9 @@ const Game = () => { display: 'flex', justifyContent: 'center', alignItems: 'center', + '.MuiLinearProgress-barColorPrimary': { + bgcolor: '#EE6D72', + }, }}/> {/* Barra de progreso */} )} @@ -175,7 +170,7 @@ const Game = () => { {question}
- {image !== null && image !== "" && Imagen de la pregunta} + {image !== null && image !== "" && Imagen de la pregunta}
{options.map((option, index) => ( @@ -184,6 +179,7 @@ const Game = () => { style={{ width: '100%', height: '17vh', + backgroundColor: selectedOption === option ? answerCorrect @@ -191,9 +187,14 @@ const Game = () => { : '#FF1744' // Red for incorrect answer : highlightedCorrectOption === option ? '#00C853' // Green for correct option if user was wrong - : '#FCF5B8', // Default background color - color: '#413C3C', + : '#EE6D72', // Default background color + color: '#FFFFFF ', fontWeight: 'bold', + transition: 'transform 0.3s ease', + '&:hover': { + backgroundColor: '#f8b6bc', + transform: 'scale(1.1)' + } }} variant="contained" onClick={!isTimeRunning ? null : () => handleOptionClick(option)} @@ -209,13 +210,13 @@ const Game = () => {
{waiting && ( - - Cargando siguiente pregunta, espere... + + {t("textoCargando")} )}
-
+ ); }; diff --git a/webapp/src/components/GameConfiguration.js b/webapp/src/components/GameConfiguration.js index 6872a47a..e2afcb59 100644 --- a/webapp/src/components/GameConfiguration.js +++ b/webapp/src/components/GameConfiguration.js @@ -2,9 +2,11 @@ import React, { useState } from 'react'; import axios from 'axios'; import { useNavigate} from 'react-router-dom'; -import { Container, Typography, TextField, Button, Snackbar, FormControl, InputLabel, Select, MenuItem} from '@mui/material'; +import { Typography, TextField, Button, Snackbar, FormControl, InputLabel, Select, MenuItem} from '@mui/material'; import '../App.css'; import { useTranslation } from 'react-i18next'; +import { CustomContainer } from '../CustomContainer'; + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -73,22 +75,14 @@ const GameConfiguration = () => { }; return ( - +
- + {t("textoPersonalizar")} { value={valueQuestion} type="number" step="1" - sx={{ width: '50vh', marginBottom: 4, marginTop: 3, backgroundColor: '#FFFFFF'}} + sx={{ + width: '30vh', + marginTop: 3, + marginBottom: 4, + backgroundColor: '#FFFFFF', + '& .MuiOutlinedInput-root': { + '&.Mui-focused fieldset': { + borderColor: '#EE6D72', + }, + }, + '& .MuiInputLabel-root': { + '&.Mui-focused': { + color: '#EE6D72', + }, + }, + }} inputProps={{ inputMode: 'numeric', pattern: '[0-9]*', @@ -108,25 +117,54 @@ const GameConfiguration = () => { - + {t("textoTematicas")} - - Temáticas + + {t("tematicas")} - @@ -149,7 +191,7 @@ const GameConfiguration = () => { setError('')} message={`Error: ${error}`} /> )}
-
+ ); }; diff --git a/webapp/src/components/Gamehistory.js b/webapp/src/components/Gamehistory.js index 2dc6a406..f8194e5a 100644 --- a/webapp/src/components/Gamehistory.js +++ b/webapp/src/components/Gamehistory.js @@ -1,9 +1,11 @@ import React, { useState, useEffect, useCallback } from 'react'; import axios from 'axios'; import { useUser } from './UserContext'; -import { Container, Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; +import { Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; import '../App.css'; import { useTranslation } from 'react-i18next'; +import { CustomContainer } from '../CustomContainer'; + const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -33,16 +35,9 @@ const Gamehistory = () => { }, [getGameHistory]); return ( - - + + {t("textoHistorico")} @@ -74,7 +69,7 @@ const Gamehistory = () => { setError('')} message={`Error: ${error}`} /> )} - + ); }; diff --git a/webapp/src/components/Login.js b/webapp/src/components/Login.js index 3dfb2508..0233e41d 100644 --- a/webapp/src/components/Login.js +++ b/webapp/src/components/Login.js @@ -2,11 +2,14 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; -import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; +import { Typography, TextField, Button, Snackbar } from '@mui/material'; import { useUser } from './UserContext'; import '../App.css'; import { useTranslation } from 'react-i18next'; +import { CustomContainer } from '../CustomContainer'; + + const Login = () => { @@ -62,7 +65,7 @@ const Login = () => { return ( - { null ) : ( -
- - {t("login")} - - setUsername(e.target.value)} - sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} - /> - setPassword(e.target.value)} - sx={{ marginBottom: 4, backgroundColor: '#FFFFFF'}} - /> - - - {error && ( - setError('')} message={`Error: ${error}`} /> - )} -
+ +
+ + {t("login")} + + setUsername(e.target.value)} + sx={{ + marginBottom: 4, + backgroundColor: '#FFFFFF', + '& .MuiOutlinedInput-root': { + '&.Mui-focused fieldset': { + borderColor: '#EE6D72', + }, + }, + '& .MuiInputLabel-root': { + '&.Mui-focused': { + color: '#EE6D72', + }, + }, + }} + /> + setPassword(e.target.value)} + sx={{ + marginBottom: 4, + backgroundColor: '#FFFFFF', + '& .MuiOutlinedInput-root': { + '&.Mui-focused fieldset': { + borderColor: '#EE6D72', + }, + }, + '& .MuiInputLabel-root': { + '&.Mui-focused': { + color: '#EE6D72', + }, + }, + }} + /> + + + {error && ( + setError('')} message={`Error: ${error}`} /> + )} +
)} -
+ ); }; diff --git a/webapp/src/components/PantallaInicio.js b/webapp/src/components/PantallaInicio.js index b6ada6e6..781b6ac3 100644 --- a/webapp/src/components/PantallaInicio.js +++ b/webapp/src/components/PantallaInicio.js @@ -1,10 +1,12 @@ -import React, { useState } from 'react'; -import { Container, Typography, Button, Box, Snackbar } from '@mui/material'; + +import { Typography, Button, Box } from '@mui/material'; import { useUser } from './UserContext'; import { useNavigate } from 'react-router-dom'; import NewGameIcon from '@mui/icons-material/SportsEsports'; import '../App.css'; import { useTranslation } from 'react-i18next'; +import { CustomContainer } from '../CustomContainer'; + const PantallaInicio = () => { @@ -19,15 +21,7 @@ const PantallaInicio = () => { } return ( - + { justifyContent: 'center', // Centra horizontalmente alignItems: 'center' }}> - + {t("textoInicio")} {usernameGlobal}! - - + ); }; diff --git a/webapp/src/components/PantallaInicioAdmin.js b/webapp/src/components/PantallaInicioAdmin.js index 6ba2dd2f..691e4e3a 100644 --- a/webapp/src/components/PantallaInicioAdmin.js +++ b/webapp/src/components/PantallaInicioAdmin.js @@ -1,8 +1,10 @@ -import React, { useState } from 'react'; -import { Container, Button, Box, Snackbar } from '@mui/material'; +import { Button, Box, Typography } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import '../App.css'; +import { CustomContainer } from '../CustomContainer'; +import { useUser } from './UserContext'; + const PantallaInicio = () => { @@ -10,6 +12,7 @@ const PantallaInicio = () => { const [t] = useTranslation("global"); const navigate = useNavigate(); + const { usernameGlobal} = useUser(); const showAllUsers = () => { @@ -22,16 +25,7 @@ const PantallaInicio = () => { return ( - + { justifyContent: 'center', // Centra horizontalmente alignItems: 'center' }}> - - - + ); }; diff --git a/webapp/src/components/Perfil.js b/webapp/src/components/Perfil.js index 49fefae4..34b28930 100644 --- a/webapp/src/components/Perfil.js +++ b/webapp/src/components/Perfil.js @@ -1,9 +1,10 @@ import axios from 'axios'; import React, { useState, useEffect, useCallback } from 'react'; -import { Container, Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; +import { Typography, TableContainer, Table, TableHead, TableBody, TableRow, TableCell, Paper, Snackbar } from '@mui/material'; import { useUser } from './UserContext'; import { useTranslation } from 'react-i18next'; import '../App.css'; +import { CustomContainer } from '../CustomContainer'; const Perfil = () => { @@ -38,18 +39,9 @@ const Perfil = () => { return ( - - + + {t("textoPerfil")} @@ -75,7 +67,7 @@ const Perfil = () => { setError('')} message={`Error: ${error}`} /> )} - + ); }; diff --git a/webapp/src/components/Ranking.js b/webapp/src/components/Ranking.js index 6365de4a..3f7323b4 100644 --- a/webapp/src/components/Ranking.js +++ b/webapp/src/components/Ranking.js @@ -1,7 +1,6 @@ import React, { useState, useEffect, useCallback} from 'react'; import axios from 'axios'; import { - Container, Box, Typography, Grid, @@ -19,6 +18,7 @@ import { } from '@mui/material'; import '../App.css'; import { useTranslation } from 'react-i18next'; +import { CustomContainer } from '../CustomContainer'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -70,19 +70,17 @@ const Ranking = () => { getRankingGlobal(); }, [getRanking, getRankingGlobal]); + const sortByLabels = { + ratio: t("ratioRanking"), + totalQuestionsAnswered: t("pregRespRanking"), + totalRightQuestions: t("aciertosRanking"), + totalGamesPlayed: t("partJugRanking"), + totalTime: t("tiempoJugadoRanking"), + }; + return ( - - + + {t("textoTop")} @@ -90,7 +88,7 @@ const Ranking = () => { {ranking && ( - Primero + Primero {ranking.primero} )} @@ -99,7 +97,7 @@ const Ranking = () => { {ranking && ( - Segundo + Segundo {ranking.segundo} )} @@ -107,7 +105,7 @@ const Ranking = () => { {ranking && ( - Tercero + Tercero {ranking.tercero} )} @@ -128,19 +126,31 @@ const Ranking = () => { > - - Ordenar por + + {t("ordenar")} @@ -152,12 +162,25 @@ const Ranking = () => { value={userLimit} type="number" step="1" - label="Número de usuarios" + label={t("numUsuarios")} inputProps={{ inputMode: 'numeric', min: 1, max: 20, }} + sx={{ + backgroundColor: '#FFFFFF', + '& .MuiOutlinedInput-root': { + '&.Mui-focused fieldset': { + borderColor: '#EE6D72', + }, + }, + '& .MuiInputLabel-root': { + '&.Mui-focused': { + color: '#EE6D72', + }, + }, + }} /> @@ -166,21 +189,11 @@ const Ranking = () => { - Posición - Usuario + {t("posicion")} + {t("usuario")} - {sortBy === "ratio" - ? "Ratio" - : sortBy === "totalQuestionsAnswered" - ? "Preguntas respondidas" - : sortBy === "totalRightQuestions" - ? "Aciertos" - : sortBy === "totalGamesPlayed" - ? "Partidas jugadas" - : sortBy === "totalTime" - ? "Tiempo total" - : ""} + {sortByLabels[sortBy] || ""} @@ -213,7 +226,7 @@ const Ranking = () => { setError('')} message={`Error: ${error}`} /> )} - + ); }; diff --git a/webapp/src/components/Ranking.test.js b/webapp/src/components/Ranking.test.js index 6c6b2023..6cc434e2 100644 --- a/webapp/src/components/Ranking.test.js +++ b/webapp/src/components/Ranking.test.js @@ -8,8 +8,10 @@ import { UserProvider } from './UserContext'; import { I18nextProvider } from "react-i18next"; import i18n from "../translations/i18n"; +i18n.changeLanguage("es"); const mock = new MockAdapter(axios); + describe('Ranking test', () => { it('renders correctly', async () => { mock.onGet('http://localhost:8000/topUsers').reply(200, { @@ -54,8 +56,8 @@ describe('Ranking test', () => { ); await waitFor(() => { - let error = getByText("Error: Error al obtener el ranking"); - expect(error).toBeInTheDocument(); + let error = getByText("Error: Error al obtener el ranking"); + expect(error).toBeInTheDocument(); }); }); }); diff --git a/webapp/src/components/fragments/Footer.js b/webapp/src/components/fragments/Footer.js index 68e29e20..989adc74 100644 --- a/webapp/src/components/fragments/Footer.js +++ b/webapp/src/components/fragments/Footer.js @@ -9,12 +9,12 @@ const Footer = () => { px={8} mt={8} sx={{ - backgroundColor: '#9A77B0', + backgroundColor: '#f8b6bc', color: 'black', textAlign: 'center' }} > - © {new Date().getFullYear()} ASW - WIQ_ES2C + © {new Date().getFullYear()} ASW - WIQ_ES2C ); } diff --git a/webapp/src/components/fragments/MenuLateral.js b/webapp/src/components/fragments/MenuLateral.js new file mode 100644 index 00000000..b08bdda9 --- /dev/null +++ b/webapp/src/components/fragments/MenuLateral.js @@ -0,0 +1,251 @@ +import { useState } from 'react'; +import { AppBar, Toolbar, IconButton, Drawer, List, ListItem, Box, Typography, Select, MenuItem, FormControl, Avatar, Button } from '@mui/material'; +import MenuIcon from '@mui/icons-material/Menu'; + +import { useTranslation } from 'react-i18next'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useUser } from '../UserContext'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import HistoryIcon from '@mui/icons-material/History'; +import StarIcon from '@mui/icons-material/Star'; +import AccountCircleIcon from '@mui/icons-material/AccountCircle'; +import LogoutIcon from '@mui/icons-material/Logout'; + + + +function MenuLateral() { + const [drawerOpen, setDrawerOpen] = useState(false); + const [language, setLanguage] = useState(''); + + const [t, i18n] = useTranslation("global"); + + const navigate = useNavigate(); + const { usernameGlobal, setUsernameGlobal } = useUser(); + + const [setError] = useState(''); + + const location = useLocation(); + + const isHiddenRoute = location.pathname === '/' || location.pathname === '/App' || location.pathname === '/Game'; + + + const toggleDrawer = (open) => (event) => { + setDrawerOpen(open); + }; + + const handleChange = (event) => { + setLanguage(event.target.value); + }; + + const showInicio = () => { + if(usernameGlobal === 'admin'){ + navigate("/PantallaInicioAdmin"); + } else { + navigate("/PantallaInicio"); + } + }; + + const showGameHistory = () => { + navigate("/Gamehistory") + }; + + const showPerfil = () => { + navigate("/Perfil") + } + + const showRanking = () => { + navigate("/Ranking") + }; + + const showLogout = () => { + try { + setUsernameGlobal(''); + navigate('/App'); + } catch (error) { + setError(error.response.data.error); + } + }; + + const list = () => ( + + + + + BrainWIQ + + + + + + {t("idioma")}: + + + + + + ); + + const listaCompleta = () => ( + + + + + BrainWIQ + + + + + + {t("idioma")}: + + + + + + {t("toolHistorico")} + + + + + + {t("toolRanking")} + + + + + + {t("toolPerfil")} + + + + + + {t("toolLogOut")} + + + + + + ); + + if (location.pathname === '/App' || location.pathname === '/') { + return ( +
+ + + + + + + BrainWIQ + + {/* Rest of your AppBar/Toolbar components */} + + + + {list()} + +
+ ); + } + + + if(location.pathname === '/Game') { + return ( +
+ + + + + + + {/* Rest of your AppBar/Toolbar components */} + + + + {list()} + +
+ ); + } + + if (isHiddenRoute) { + return null; + } + + return ( +
+ + + + + + + {/* Rest of your AppBar/Toolbar components */} + + + + {listaCompleta()} + +
+ ); +} + +export default MenuLateral; \ No newline at end of file diff --git a/webapp/src/components/fragments/MenuLateral.test.js b/webapp/src/components/fragments/MenuLateral.test.js new file mode 100644 index 00000000..42e8171f --- /dev/null +++ b/webapp/src/components/fragments/MenuLateral.test.js @@ -0,0 +1,79 @@ +import React from 'react'; +import { render, fireEvent, act, waitFor } from '@testing-library/react'; +import MenuLateral from './MenuLateral'; // Asegúrate de que la ruta es correcta +import { I18nextProvider } from "react-i18next"; +import i18n from "../../translations/i18n"; +import { UserProvider } from '../UserContext'; +import { MemoryRouter as Router, useLocation } from 'react-router-dom'; + +i18n.changeLanguage("es"); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(), +})); + +describe('MenuLateral', () => { + it('renders the complete list', async() => { + const customLocation = { pathname: '/PantallaInicio', search: '', hash: '', state: null }; + useLocation.mockReturnValue(customLocation); + const toggleDrawer = jest.fn(); + + const { getByLabelText } = render( + + + + + + + + ); + + const iconButton = getByLabelText('menu3'); + + await act(() => { + fireEvent.click(iconButton); + }); + }); + + it('renders MenuLateral', async() => { + const customLocation = { pathname: '/', search: '', hash: '', state: null }; + useLocation.mockReturnValue(customLocation); + + const { getByRole, getByText } = render( + + + + + + + + ); + const appBar = getByRole('banner'); + expect(appBar).toBeInTheDocument(); + + await act(() => { + const menuButton = getByText('BrainWIQ'); + fireEvent.click(menuButton); + }); + }); + + it('muestra la barra de navegacion correctamente en /', async () => { + const customLocation = { pathname: '/Game', search: '', hash: '', state: null }; + useLocation.mockReturnValue(customLocation); + + const { getByText } = render( + + + + + + + + ); + + await act(() => { + const menuButton = getByText('BrainWIQ2'); + fireEvent.click(menuButton); + }); + }); +}); \ No newline at end of file diff --git a/webapp/src/components/fragments/NavigationBar.js b/webapp/src/components/fragments/NavigationBar.js deleted file mode 100644 index 2e94ce17..00000000 --- a/webapp/src/components/fragments/NavigationBar.js +++ /dev/null @@ -1,204 +0,0 @@ -// src/components/NavigationBar.js -import React, { useState } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { AppBar, Toolbar, IconButton, Menu, MenuItem, Grid, Button, Hidden} from '@mui/material'; -import Tooltip from '@mui/material/Tooltip'; -import { useUser } from '../UserContext'; -import MenuIcon from '@mui/icons-material/Menu'; -import { useTranslation } from 'react-i18next'; - - -const NavigationBar = () => { - - const [t, i18n] = useTranslation("global"); - - const [setError] = useState(''); - const { usernameGlobal, setUsernameGlobal } = useUser(); - const navigate = useNavigate(); - - const location = useLocation(); - - const isHiddenRoute = location.pathname === '/' || location.pathname === '/App' || location.pathname === '/Game'; - - const [anchorEl, setAnchorEl] = React.useState(null); - const open = Boolean(anchorEl); - - const handleMenu = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const showHome = () => { - usernameGlobal === 'admin' ? navigate("/PantallaInicioAdmin") : navigate("/PantallaInicio"); - }; - - const showGameHistory = () => { - navigate("/Gamehistory") - }; - - const showPerfil = () => { - navigate("/Perfil") - } - - const showRanking = () => { - navigate("/Ranking") - }; - - const showLogout = () => { - try { - setUsernameGlobal(''); - navigate('/App'); - } catch (error) { - setError(error.response.data.error); - } - }; - - if (location.pathname === '/App' || location.pathname === '/') { - return ( - - - - - - - i18n.changeLanguage('es')}>Español - i18n.changeLanguage('en')}>Inglés - - - - - - - - - - - - - - ); - } - - if(location.pathname === '/Game') { - return ( - - - - - - - Inicio - - - - - - - - - ); - } - - - if (isHiddenRoute) { - return null; - } - - return ( - - - - - - - Inicio - Historial de Juegos - Ranking - Perfil - Cerrar Sesión - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -export default NavigationBar; \ No newline at end of file diff --git a/webapp/src/components/fragments/NavigationBar.test.js b/webapp/src/components/fragments/NavigationBar.test.js deleted file mode 100644 index adc64ccc..00000000 --- a/webapp/src/components/fragments/NavigationBar.test.js +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import { render, waitFor, act, fireEvent} from '@testing-library/react'; -import { MemoryRouter as Router, useLocation } from 'react-router-dom'; -import { UserProvider } from '../UserContext'; -import NavigationBar from './NavigationBar'; -import { I18nextProvider } from "react-i18next"; -import i18n from "../../translations/i18n"; - -i18n.changeLanguage("es"); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useLocation: jest.fn(), -})); - -describe('Navigation bar component', () => { - it('muestra la barra de navegacion correctamente en /', async () => { - const customLocation = { pathname: '/', search: '', hash: '', state: null }; - useLocation.mockReturnValue(customLocation); - - const { getByAltText, getByRole, getByText } = render( - - - - - - ); - - var spanishButton = null; - var englishButton = null; - - await waitFor(() => { - spanishButton = getByAltText('Imagen español'); - expect(spanishButton).toBeInTheDocument(); - expect(spanishButton).toHaveAttribute('src', 'esp.png'); - - englishButton = getByAltText('Imagen ingles'); - expect(englishButton).toBeInTheDocument(); - expect(englishButton).toHaveAttribute('src', 'ing.png'); - }); - - await act(async() => { - let iconButton = getByRole('button', { name: 'menu' }); - fireEvent.click(iconButton); - - // Simulamos un cambio de idioma - fireEvent.click(spanishButton); - fireEvent.click(englishButton); - }); - }); - - it('muestra la barra de navegacion correctamente en /Game', async () => { - const customLocation = { pathname: '/Game', search: '', hash: '', state: null }; - useLocation.mockReturnValue(customLocation); - - const { getByAltText } = render( - - - - - - ); - - var homeButton = null; - - await waitFor(() => { - homeButton = getByAltText('Imagen home'); - expect(homeButton).toBeInTheDocument(); - expect(homeButton).toHaveAttribute('src', 'home.png'); - }); - - await act(async() => { - fireEvent.click(homeButton); - }); - }); -}); \ No newline at end of file diff --git a/webapp/src/components/images/brain-angry.png b/webapp/src/components/images/brain-angry.png new file mode 100644 index 00000000..e6225e64 Binary files /dev/null and b/webapp/src/components/images/brain-angry.png differ diff --git a/webapp/src/components/images/brain-icon.webp b/webapp/src/components/images/brain-icon.webp new file mode 100644 index 00000000..9aca8f32 Binary files /dev/null and b/webapp/src/components/images/brain-icon.webp differ diff --git a/webapp/src/components/images/brain-icon2.ico b/webapp/src/components/images/brain-icon2.ico new file mode 100644 index 00000000..3c569191 Binary files /dev/null and b/webapp/src/components/images/brain-icon2.ico differ diff --git a/webapp/src/components/images/brain-icon2.png b/webapp/src/components/images/brain-icon2.png new file mode 100644 index 00000000..3c569191 Binary files /dev/null and b/webapp/src/components/images/brain-icon2.png differ diff --git a/webapp/src/components/images/brain-strong.png b/webapp/src/components/images/brain-strong.png new file mode 100644 index 00000000..33f24d0f Binary files /dev/null and b/webapp/src/components/images/brain-strong.png differ diff --git a/webapp/src/components/images/brain-strong2.png b/webapp/src/components/images/brain-strong2.png new file mode 100644 index 00000000..5b222d46 Binary files /dev/null and b/webapp/src/components/images/brain-strong2.png differ diff --git a/webapp/src/components/images/brain-top.png b/webapp/src/components/images/brain-top.png new file mode 100644 index 00000000..e1e7eb8f Binary files /dev/null and b/webapp/src/components/images/brain-top.png differ diff --git a/webapp/src/components/images/brain-top2.png b/webapp/src/components/images/brain-top2.png new file mode 100644 index 00000000..64fdefa1 Binary files /dev/null and b/webapp/src/components/images/brain-top2.png differ diff --git a/webapp/src/components/images/brain-top3.png b/webapp/src/components/images/brain-top3.png new file mode 100644 index 00000000..06e7fab1 Binary files /dev/null and b/webapp/src/components/images/brain-top3.png differ diff --git a/webapp/src/components/images/esp.png b/webapp/src/components/images/esp.png deleted file mode 100644 index b7d2fb17..00000000 Binary files a/webapp/src/components/images/esp.png and /dev/null differ diff --git a/webapp/src/components/images/fondo.png b/webapp/src/components/images/fondo.png new file mode 100644 index 00000000..a1b3aedb Binary files /dev/null and b/webapp/src/components/images/fondo.png differ diff --git a/webapp/src/components/images/ing.png b/webapp/src/components/images/ing.png deleted file mode 100644 index 796d208b..00000000 Binary files a/webapp/src/components/images/ing.png and /dev/null differ diff --git a/webapp/src/index.js b/webapp/src/index.js index 557a5d4f..8ea55531 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -2,7 +2,7 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; -import NavigationBar from './components/fragments/NavigationBar'; +import MenuLateral from './components/fragments/MenuLateral'; import Footer from './components/fragments/Footer'; import reportWebVitals from './reportWebVitals'; import { UserProvider } from './components/UserContext'; @@ -49,24 +49,24 @@ root.render( - - - }> - }> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - -
+ + + }> + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
diff --git a/webapp/src/index.test.js b/webapp/src/index.test.js index 50858328..ce1bc53b 100644 --- a/webapp/src/index.test.js +++ b/webapp/src/index.test.js @@ -4,7 +4,7 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { I18nextProvider } from 'react-i18next'; import { UserProvider } from './components/UserContext'; import i18next from './translations/i18n'; -import NavigationBar from './components/fragments/NavigationBar'; +import MenuLateral from './components/fragments/MenuLateral'; import Footer from './components/fragments/Footer'; import PantallaInicio from './components/PantallaInicio'; import PantallaInicioAdmin from './components/PantallaInicioAdmin'; @@ -27,7 +27,7 @@ it('renders all routes correctly', () => { - + } /> } /> diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 501daf1d..aac2e7f6 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -48,5 +48,25 @@ "textoRespuesta": "Answer", "mensajeAddOk": "User added successfully", "mensajeAddFail": "An user with that name has already registered", - "mensajeLogOut": "Closed session" + "mensajeLogOut": "Closed session", + "idioma": "Language", + "seleccionar": "Choose", + "ingles": "English", + "espanol": "Spanish", + "tematicas": "Themes", + "textoCargando": "Loading next question, wait...", + "textoEstadisticas": "Last game statistics", + "correctas": "Correct questions: ", + "incorrectas": "Incorrect questions: ", + "ratio": "Hit ratio: ", + "tiempoTotal": "Total time: ", + "botonResumen": "BACK TO TOP", + "ordenar": "Order by", + "posicion": "Position", + "numUsuarios": "Number of users", + "ratioRanking": "Ratio", + "aciertosRanking": "Successes", + "pregRespRanking": "Questions answered", + "partJugRanking": "Games played", + "tiempoJugadoRanking": "Time played" } \ No newline at end of file diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 3096a9f9..3476aa2f 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -1,5 +1,5 @@ { - "login": "Inicia sesión", + "login": "INICIA SESIÓN", "usuario": "Usuario", "password": "Contraseña", "botonLogin": "ENTRA", @@ -48,6 +48,29 @@ "textoRespuesta": "Respuesta", "mensajeAddOk": "Usuario añadido correctamente", "mensajeAddFail": "Ya se ha registrado un usuario con ese nombre", - "mensajeLogOut": "Sesion cerrada" + "mensajeLogOut": "Sesion cerrada", + "idioma": "Idioma", + "seleccionar": "Seleccionar", + "ingles": "Inglés", + "espanol": "Español", + "tematicas": "Temáticas", + "textoCargando": "Cargando siguiente pregunta, espere...", + "textoEstadisticas": "Estadísticas de la última partida", + "correctas": "Preguntas correctas: ", + "incorrectas": "Preguntas incorrectas: ", + "ratio": "Ratio de aciertos: ", + "tiempoTotal": "Tiempo total: ", + "botonResumen": "VOLVER AL INICIO", + "ordenar": "Ordenar por", + "posicion": "Posición", + "numUsuarios": "Número de usuarios", + "ratioRanking": "Ratio", + "aciertosRanking": "Aciertos", + "pregRespRanking": "Preguntas respondidas", + "partJugRanking": "Partidas jugadas", + "tiempoJugadoRanking": "Tiempo jugado" + + + }