diff --git a/docs/src/01_introduction_and_goals.adoc b/docs/src/01_introduction_and_goals.adoc index 0eebb332..4f06af2e 100644 --- a/docs/src/01_introduction_and_goals.adoc +++ b/docs/src/01_introduction_and_goals.adoc @@ -18,9 +18,13 @@ These include **** === Requirements Overview -The system will follow the functionality of the "Saber y Ganar" TV program, and so it will allow the users to select an answer between some options. -The questions and answers will be automatically generated using the WikiData API, that will also determine which of the answers is in fact the correct one. -The system will also store the historical data of the users and will be accessible through the web. +The system will follow the functionality of the "Saber y Ganar" TV program, and so it will allow the users to select an answer between some options. + +The questions and answers will be automatically generated using the WikiData API, that will also determine which of the answers is in fact the correct one. + +In adition to this, the system will also store the historical data of the users, thanks to the login and registering service featured in the application, and users will be able to access their information. + +The application will have at least English as an available language. + +Moreover, the project will be stored and deployed through GitHub. + +Lastly, the system will give access to non sensitive user's information through a public API as well as another API for the questions generated. + [role="arc42help"] **** @@ -53,7 +57,7 @@ See https://docs.arc42.org/section-1/[Introduction and Goals] in the arc42 docum |Quality attribute|Scenario | Usability | The user must be able to understand the function of the application before the minute mark. | Performance | The application will be able to operate within reasonable response times, taking into account the already present waiting times (time to answer, between questions, etc). -| Security | The information stored about a user can only be accessed by said user, never others. +| Security & Privacy | The information stored about a user can only be accessed by said user, never others. | Robustness | The application will be able to handle any user error that could happen at runtime. | Accessibility | The application will be accessible by all users, even if the suffer from visual impediments such as colorblindness. |=== diff --git a/docs/src/06_runtime_view.adoc b/docs/src/06_runtime_view.adoc index bc687266..d9f01a78 100644 --- a/docs/src/06_runtime_view.adoc +++ b/docs/src/06_runtime_view.adoc @@ -47,7 +47,7 @@ entity Backend as "Webapp Backend" database Wikidata User -> Frontend: next question Frontend -> Backend: get question -Backend -> Wikidata: request data +Backend -> Wikidata: request data\n(1s timeout) Wikidata -> Backend: receive data Backend -> Backend: generate question Backend -> Frontend: return question and answers @@ -62,11 +62,11 @@ actor User entity Frontend as "Webapp Frontend" entity Backend as "Webapp Backend" database DB -User -> Frontend: answer -Frontend -> Backend: forward answer -Backend -> Backend: process answer -Backend -> DB: store result -Backend -> Frontend: return correctness +User -> Frontend: answer\n(30s timelimit) +Frontend -> Frontend: process answer +Frontend -> Backend: forward result +Backend -> DB: store result\n(1s timeout) +Backend -> Frontend: answer Frontend -> User: show correctness ---- @@ -80,7 +80,7 @@ entity Backend as "Webapp Backend" database DB User -> Frontend: send credentials Frontend -> Backend: forward credentials -Backend -> DB: query for username +Backend -> DB: query for username\n(1s timeout) DB -> Backend: retrieve user data Backend -> Backend: validate password Backend -> Frontend: login successful @@ -97,7 +97,7 @@ entity Backend as "Webapp Backend" database DB User -> Frontend: send credentials Frontend -> Backend: forward credentials -Backend -> DB: query for username +Backend -> DB: query for username\n(1s timeout) DB -> Backend: retrieve user data Backend-> Backend: validate password Backend -> Frontend: wrong password @@ -116,9 +116,9 @@ entity Backend as "Webapp Backend" database DB User -> Frontend: send signup form Frontend -> Backend: forward signup form -Backend -> DB: check if username exists +Backend -> DB: check if username exists\n(1s timeout) DB -> Backend: answer -Backend -> DB: insert data +Backend -> DB: insert data\n(1s timeout) Backend -> Frontend: signup successful Frontend -> User: signup successful ---- @@ -133,7 +133,7 @@ entity Backend as "Webapp Backend" database DB User -> Frontend: send signup form Frontend -> Backend: forward signup form -Backend -> DB: check if username exists +Backend -> DB: check if username exists\n(1s timeout) DB -> Backend: answer Backend -> Frontend: error: username in use Frontend -> User: username in use @@ -153,7 +153,7 @@ database Wikidata User -> Frontend: next question Frontend -> Backend: get question Backend -> Wikidata: request data -Wikidata -> Backend: error +Wikidata -> Backend: error/timeout Backend -> Backend: error handling Backend -> Frontend: wikidata error Frontend -> User: show error message @@ -170,7 +170,7 @@ database DB User -> Frontend: request Frontend -> Backend: request Backend -> DB: request -DB -> Backend: error +DB -> Backend: error/timeout Backend -> Backend: error handling Backend -> Frontend: DB error Frontend -> User: show error message diff --git a/package-lock.json b/package-lock.json index ca66d22c..737d6d39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", "asciidoctor-emoji": "^0.5.0", + "i18n": "^0.15.1", + "react-countdown": "^2.3.5", "react-router-dom": "^6.22.1" } }, @@ -212,6 +214,45 @@ "react-dom": "^16.8.0 || ^17.0.0" } }, + "node_modules/@messageformat/core": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@messageformat/core/-/core-3.3.0.tgz", + "integrity": "sha512-YcXd3remTDdeMxAlbvW6oV9d/01/DZ8DHUFwSttO3LMzIZj3iO0NRw+u1xlsNNORFI+u0EQzD52ZX3+Udi0T3g==", + "dependencies": { + "@messageformat/date-skeleton": "^1.0.0", + "@messageformat/number-skeleton": "^1.0.0", + "@messageformat/parser": "^5.1.0", + "@messageformat/runtime": "^3.0.1", + "make-plural": "^7.0.0", + "safe-identifier": "^0.4.1" + } + }, + "node_modules/@messageformat/date-skeleton": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.0.1.tgz", + "integrity": "sha512-jPXy8fg+WMPIgmGjxSlnGJn68h/2InfT0TNSkVx0IGXgp4ynnvYkbZ51dGWmGySEK+pBiYUttbQdu5XEqX5CRg==" + }, + "node_modules/@messageformat/number-skeleton": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz", + "integrity": "sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==" + }, + "node_modules/@messageformat/parser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.0.tgz", + "integrity": "sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==", + "dependencies": { + "moo": "^0.5.1" + } + }, + "node_modules/@messageformat/runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.1.tgz", + "integrity": "sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==", + "dependencies": { + "make-plural": "^7.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.1.tgz", @@ -267,6 +308,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "peer": true }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -298,6 +344,22 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -312,6 +374,17 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/fast-printf": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.9.tgz", + "integrity": "sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==", + "dependencies": { + "boolean": "^3.1.4" + }, + "engines": { + "node": ">=10.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -355,6 +428,25 @@ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" }, + "node_modules/i18n": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/i18n/-/i18n-0.15.1.tgz", + "integrity": "sha512-yue187t8MqUPMHdKjiZGrX+L+xcUsDClGO0Cz4loaKUOK9WrGw5pgan4bv130utOwX7fHE9w2iUeHFalVQWkXA==", + "dependencies": { + "@messageformat/core": "^3.0.0", + "debug": "^4.3.3", + "fast-printf": "^1.6.9", + "make-plural": "^7.0.0", + "math-interval-parser": "^2.0.1", + "mustache": "^4.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/mashpie" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -479,6 +571,19 @@ "loose-envify": "cli.js" } }, + "node_modules/make-plural": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.3.0.tgz", + "integrity": "sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw==" + }, + "node_modules/math-interval-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz", + "integrity": "sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -491,6 +596,24 @@ "node": ">=10" } }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -541,6 +664,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-countdown": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/react-countdown/-/react-countdown-2.3.5.tgz", + "integrity": "sha512-K26ENYEesMfPxhRRtm1r+Pf70SErrvW3g4CArLi/x6MPFjgfDFYePT4UghEj8p2nI0cqVV7/JjDgjyr//U60Og==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": ">= 15", + "react-dom": ">= 15" + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -610,6 +745,11 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/safe-identifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", + "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==" + }, "node_modules/scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", diff --git a/package.json b/package.json index c7801602..37679c9a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", "asciidoctor-emoji": "^0.5.0", + "i18n": "^0.15.1", + "react-countdown": "^2.3.5", "react-router-dom": "^6.22.1" } } diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 7933cdc3..ad42295e 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -15,8 +15,11 @@ "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "axios": "^1.6.5", + "i18n": "^0.15.1", "react": "^18.2.0", + "react-countdown": "^2.3.5", "react-dom": "^18.2.0", + "react-i18next": "^14.0.5", "react-icons": "^5.0.1", "react-router-dom": "^6.22.2", "react-scripts": "^5.0.1", @@ -1970,9 +1973,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", - "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -4514,6 +4517,45 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@messageformat/core": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@messageformat/core/-/core-3.3.0.tgz", + "integrity": "sha512-YcXd3remTDdeMxAlbvW6oV9d/01/DZ8DHUFwSttO3LMzIZj3iO0NRw+u1xlsNNORFI+u0EQzD52ZX3+Udi0T3g==", + "dependencies": { + "@messageformat/date-skeleton": "^1.0.0", + "@messageformat/number-skeleton": "^1.0.0", + "@messageformat/parser": "^5.1.0", + "@messageformat/runtime": "^3.0.1", + "make-plural": "^7.0.0", + "safe-identifier": "^0.4.1" + } + }, + "node_modules/@messageformat/date-skeleton": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.0.1.tgz", + "integrity": "sha512-jPXy8fg+WMPIgmGjxSlnGJn68h/2InfT0TNSkVx0IGXgp4ynnvYkbZ51dGWmGySEK+pBiYUttbQdu5XEqX5CRg==" + }, + "node_modules/@messageformat/number-skeleton": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz", + "integrity": "sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==" + }, + "node_modules/@messageformat/parser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.0.tgz", + "integrity": "sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==", + "dependencies": { + "moo": "^0.5.1" + } + }, + "node_modules/@messageformat/runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.1.tgz", + "integrity": "sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==", + "dependencies": { + "make-plural": "^7.0.0" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.3.tgz", @@ -7486,6 +7528,11 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==" + }, "node_modules/boxen": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", @@ -10972,6 +11019,17 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-printf": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.9.tgz", + "integrity": "sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==", + "dependencies": { + "boolean": "^3.1.4" + }, + "engines": { + "node": ">=10.0" + } + }, "node_modules/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -12112,6 +12170,14 @@ "node": ">=12" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -12255,6 +12321,48 @@ "node": ">=10.17.0" } }, + "node_modules/i18n": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/i18n/-/i18n-0.15.1.tgz", + "integrity": "sha512-yue187t8MqUPMHdKjiZGrX+L+xcUsDClGO0Cz4loaKUOK9WrGw5pgan4bv130utOwX7fHE9w2iUeHFalVQWkXA==", + "dependencies": { + "@messageformat/core": "^3.0.0", + "debug": "^4.3.3", + "fast-printf": "^1.6.9", + "make-plural": "^7.0.0", + "math-interval-parser": "^2.0.1", + "mustache": "^4.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/mashpie" + } + }, + "node_modules/i18next": { + "version": "23.10.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.0.tgz", + "integrity": "sha512-/TgHOqsa7/9abUKJjdPeydoyDc0oTi/7u9F8lMSj6ufg4cbC1Oj3f/Jja7zj7WRIhEQKB7Q4eN6y68I9RDxxGQ==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -18675,6 +18783,11 @@ "semver": "bin/semver.js" } }, + "node_modules/make-plural": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.3.0.tgz", + "integrity": "sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw==" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -18710,6 +18823,14 @@ "node": ">=0.10.0" } }, + "node_modules/math-interval-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz", + "integrity": "sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -19107,6 +19228,11 @@ "node": ">= 14" } }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -19124,6 +19250,14 @@ "multicast-dns": "cli.js" } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -21872,6 +22006,18 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-countdown": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/react-countdown/-/react-countdown-2.3.5.tgz", + "integrity": "sha512-K26ENYEesMfPxhRRtm1r+Pf70SErrvW3g4CArLi/x6MPFjgfDFYePT4UghEj8p2nI0cqVV7/JjDgjyr//U60Og==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": ">= 15", + "react-dom": ">= 15" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -22006,6 +22152,27 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-i18next": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.0.5.tgz", + "integrity": "sha512-5+bQSeEtgJrMBABBL5lO7jPdSNAbeAZ+MlFWDw//7FnVacuVu3l9EeWFzBQvZsKy+cihkbThWOAThEdH8YjGEw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-icons": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", @@ -23844,6 +24011,11 @@ } ] }, + "node_modules/safe-identifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", + "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==" + }, "node_modules/safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", @@ -26630,6 +26802,14 @@ "node": ">= 0.8" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/webapp/package.json b/webapp/package.json index 95e0cfc9..f31b2a0c 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -10,8 +10,11 @@ "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "axios": "^1.6.5", + "i18n": "^0.15.1", "react": "^18.2.0", + "react-countdown": "^2.3.5", "react-dom": "^18.2.0", + "react-i18next": "^14.0.5", "react-icons": "^5.0.1", "react-router-dom": "^6.22.2", "react-scripts": "^5.0.1", diff --git a/webapp/src/App.css b/webapp/src/App.css deleted file mode 100644 index 74b5e053..00000000 --- a/webapp/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/webapp/src/App.js b/webapp/src/App.js index fb66bd81..a6aabb92 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -1,10 +1,16 @@ import React from 'react'; import QuestionView from './components/questionView/QuestionView'; +import GameMenu from './components/GameMenu/GameMenu'; +import Navbar from './components/fragments/NavBar'; +import Home from './components/home/Home'; +import Login from './components/loginAndRegistration/Login'; +import AddUser from './components/loginAndRegistration/AddUser'; +import Instructions from './components/Instructions'; +import './custom.css'; function App() { return ( - /* @@ -14,12 +20,12 @@ function App() { } /> } /> } /> + } /> + } /> - */ - ); } diff --git a/webapp/src/components/AddUser.test.js b/webapp/src/components/AddUser.test.js deleted file mode 100644 index 87334886..00000000 --- a/webapp/src/components/AddUser.test.js +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { render, fireEvent, screen, waitFor } from '@testing-library/react'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; -import AddUser from './AddUser'; - -const mockAxios = new MockAdapter(axios); - -describe('AddUser component', () => { - beforeEach(() => { - mockAxios.reset(); - }); - - it('should add user successfully', async () => { - render(); - - const usernameInput = screen.getByLabelText(/Username/i); - const passwordInput = screen.getByLabelText(/Password/i); - const addUserButton = screen.getByRole('button', { name: /Add User/i }); - - // Mock the axios.post request to simulate a successful response - mockAxios.onPost('http://localhost:8000/adduser').reply(200); - - // Simulate user input - fireEvent.change(usernameInput, { target: { value: 'testUser' } }); - fireEvent.change(passwordInput, { target: { value: 'testPassword' } }); - - // Trigger the add user button click - fireEvent.click(addUserButton); - - // Wait for the Snackbar to be open - await waitFor(() => { - expect(screen.getByText(/User added successfully/i)).toBeInTheDocument(); - }); - }); - - it('should handle error when adding user', async () => { - render(); - - const usernameInput = screen.getByLabelText(/Username/i); - const passwordInput = screen.getByLabelText(/Password/i); - const addUserButton = screen.getByRole('button', { name: /Add User/i }); - - // Mock the axios.post request to simulate an error response - mockAxios.onPost('http://localhost:8000/adduser').reply(500, { error: 'Internal Server Error' }); - - // Simulate user input - fireEvent.change(usernameInput, { target: { value: 'testUser' } }); - fireEvent.change(passwordInput, { target: { value: 'testPassword' } }); - - // Trigger the add user button click - fireEvent.click(addUserButton); - - // Wait for the error Snackbar to be open - await waitFor(() => { - expect(screen.getByText(/Error: Internal Server Error/i)).toBeInTheDocument(); - }); - }); -}); diff --git a/webapp/src/components/GameMenu.js b/webapp/src/components/GameMenu.js deleted file mode 100644 index e69de29b..00000000 diff --git a/webapp/src/components/GameMenu/GameMenu.js b/webapp/src/components/GameMenu/GameMenu.js new file mode 100644 index 00000000..a84fe5ea --- /dev/null +++ b/webapp/src/components/GameMenu/GameMenu.js @@ -0,0 +1,31 @@ +import "../../custom.css"; +import { Link } from "react-router-dom"; +import {useTranslation} from "react-i18next"; + +export default function GameMenu() { + const[t, i18n] = useTranslation("global"); + + return ( +
+

{t("gameMenu.title")}

+ + +
+ ); + } + + function ButtonHistoricalData({ t }) { + function handleClick() { + //ir a la vista de historical data + alert("Historical Data"); + } + return ; + } + + function ButtonNewGame({ t }) { + return ( + +

{t("gameMenu.new_game_button")}

+ + ); + } \ No newline at end of file diff --git a/webapp/src/components/Home/Home.css b/webapp/src/components/Home/Home.css deleted file mode 100644 index 98b8043a..00000000 --- a/webapp/src/components/Home/Home.css +++ /dev/null @@ -1,37 +0,0 @@ - .wrapper { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100vh; - } - - .wrapper button{ - width: 100%; - height: 45px; - background: darkblue; - border: none; - outline: none; - border-radius: 40px; - box-shadow: 0 0 10px black; - cursor:pointer; - font-size: 16px; - color: white; - font-weight: 700; - } - - .wrapper link{ - font-size: 14.5px; - text-align: center; - margin: 20px 0 15px; - } - - - - - - - - - - diff --git a/webapp/src/components/Home/Home.js b/webapp/src/components/Home/Home.js index fdabeeb1..0faf09e7 100644 --- a/webapp/src/components/Home/Home.js +++ b/webapp/src/components/Home/Home.js @@ -1,18 +1,18 @@ import React from "react"; -import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import { Link } from 'react-router-dom'; -import "./Home.css"; +import "../../custom.css"; +import {useTranslation} from "react-i18next"; -import "./Home.css"; - function Home() { + const[t, i18n] = useTranslation("global"); + return (

- Welcome to WIQ! + {t("home.welcome")}

@@ -26,24 +26,22 @@ function Home() { ); -} - - -function ButtonHowToPlay() { - return ( - - - - ); - -} + function ButtonHowToPlay() { + return ( + + + + ); + + } + function ButtonLogin() { return ( - + ); @@ -56,9 +54,12 @@ function LinkRegister() { component="button-register" variant="body2" > - Don't have an account? Register here. + {t("home.register")} ); } +} + + export default Home; diff --git a/webapp/src/components/Instructions.css b/webapp/src/components/Instructions.css deleted file mode 100644 index e381600b..00000000 --- a/webapp/src/components/Instructions.css +++ /dev/null @@ -1,21 +0,0 @@ - -main{ - background-color: white; -} - -section { - text-align: justify; - background-color: white; -} - - -article ul p{ - text-align: center; - background-color: lightblue; - font-size: large; -} - -article ul li{ - text-align: justify; - background-color: white; -} \ No newline at end of file diff --git a/webapp/src/components/Instructions.js b/webapp/src/components/Instructions.js index bee9b261..7ffc2338 100644 --- a/webapp/src/components/Instructions.js +++ b/webapp/src/components/Instructions.js @@ -1,54 +1,57 @@ import React from 'react'; -import '../components/Instructions.css'; +import '../custom.css'; +import {useTranslation} from "react-i18next"; function Instructions() { + const[t, i18n] = useTranslation("global"); + return (
-

WIQ Instructions

+

{t("instructions.title")}

-

    Objetive:

    +

      {t("instructions.objective")}

    • - The objective of the game is to answer as many questions correctly as possible. + {t("instructions.objective_p1")}
-

    How to Play:

    +

      {t("instructions.how_to_play")}

    • - The game consists of a series of questions. + {t("instructions.how_to_play_p1")}
    • - Read each question carefully. + {t("instructions.how_to_play_p2")}
    • - Choose the correct answer from the options provided. + {t("instructions.how_to_play_p3")}
    • - Click or tap on your selected answer to submit it. + {t("instructions.how_to_play_p4")}
-

    Scoring:

  • - Each correct answer earns you x points.
  • +

      {t("instructions.scoring")}

    • + {t("instructions.scoring_p1")}
    • - Incorrect answers do not deduct points. + {t("instructions.scoring_p2")}
-

    Time Limit:

  • - Some game modes may have a time limit for answering each question. Be quick and accurate to maximize your score. +

      {t("instructions.time_limit")}

    • + {t("instructions.time_limit_p1")}
-

    Have Fun!:

    +

      {t("instructions.have_fun")}

    • - Enjoy the game and test your knowledge. Good luck! + {t("instructions.have_fun_p1")}
diff --git a/webapp/src/components/Login.css b/webapp/src/components/Login.css deleted file mode 100644 index c65fa58b..00000000 --- a/webapp/src/components/Login.css +++ /dev/null @@ -1,106 +0,0 @@ - -.wrapper{ - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - background-color: lightblue; -} - -.wrapper2{ - width: 420px; - background-color: blue; - color: white; - border-style: 10px; - padding: 30px 40px; - border-radius: 15%; -} - -.wrapper2 h1{ - font-size: 36px; - text-align: center; -} - -.wrapper2 .input-box{ - position: relative; - width: 100%; - height: 50px; - margin: 30px 0; -} - -.input-box input{ - width: 100%; - height: 100%; - background: lightblue; - border: none; - outline: none; - border: 2px solid gray; - border-radius: 40px; - font-size: 14.5px; - color: black; -} - -.input-box input::placeholder{ - background-color: lightblue; -} - -.input-box .icon{ - position: absolute; - right: 20px; - top: 50%; - transform: translateY(-50%); - font-size: 16px; -} - -.wrapper2 .remember-forgot{ - display: flex; - justify-content: space-between; - font-size: 14.5px; - margin: -15px 0 15px; -} - -.remember-forgot label input{ - accent-color: white; - margin-right: 4px; -} - -.remember-forgot a { - color: white; - text-decoration: none; -} - -.remember-forgot a:hover{ - text-decoration: underline; -} - -.wrapper2 button{ - width: 100%; - height: 45px; - background: white; - border: none; - outline: none; - border-radius: 40px; - box-shadow: 0 0 10px black; - cursor:pointer; - font-size: 16px; - color: black; - font-weight: 700; -} - -.button-register { - color: aliceblue; - } - - .button-login { - color: aliceblue; - } - -.register-link p a { - color: white; - text-decoration: none; - font-weight: 600; -} - -.register-link p a:hover{ - text-decoration: underline; -} \ No newline at end of file diff --git a/webapp/src/components/Login.test.js b/webapp/src/components/Login.test.js deleted file mode 100644 index af102dcf..00000000 --- a/webapp/src/components/Login.test.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { render, fireEvent, screen, waitFor, act } from '@testing-library/react'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; -import Login from './Login'; - -const mockAxios = new MockAdapter(axios); - -describe('Login component', () => { - beforeEach(() => { - mockAxios.reset(); - }); - - it('should log in successfully', async () => { - render(); - - const usernameInput = screen.getByLabelText(/Username/i); - const passwordInput = screen.getByLabelText(/Password/i); - const loginButton = screen.getByRole('button', { name: /Login/i }); - - // Mock the axios.post request to simulate a successful response - mockAxios.onPost('http://localhost:8000/login').reply(200, { createdAt: '2024-01-01T12:34:56Z' }); - - // Simulate user input - await act(async () => { - fireEvent.change(usernameInput, { target: { value: 'testUser' } }); - fireEvent.change(passwordInput, { target: { value: 'testPassword' } }); - fireEvent.click(loginButton); - }); - - // Verify that the user information is displayed - expect(screen.getByText(/Hello testUser!/i)).toBeInTheDocument(); - expect(screen.getByText(/Your account was created on 1\/1\/2024/i)).toBeInTheDocument(); - }); - - it('should handle error when logging in', async () => { - render(); - - const usernameInput = screen.getByLabelText(/Username/i); - const passwordInput = screen.getByLabelText(/Password/i); - const loginButton = screen.getByRole('button', { name: /Login/i }); - - // Mock the axios.post request to simulate an error response - mockAxios.onPost('http://localhost:8000/login').reply(401, { error: 'Unauthorized' }); - - // Simulate user input - fireEvent.change(usernameInput, { target: { value: 'testUser' } }); - fireEvent.change(passwordInput, { target: { value: 'testPassword' } }); - - // Trigger the login button click - fireEvent.click(loginButton); - - // Wait for the error Snackbar to be open - await waitFor(() => { - expect(screen.getByText(/Error: Unauthorized/i)).toBeInTheDocument(); - }); - - // Verify that the user information is not displayed - expect(screen.queryByText(/Hello testUser!/i)).toBeNull(); - expect(screen.queryByText(/Your account was created on/i)).toBeNull(); - }); -}); diff --git a/webapp/src/components/fragments/NavBar.css b/webapp/src/components/fragments/NavBar.css deleted file mode 100644 index 4ec0f2c2..00000000 --- a/webapp/src/components/fragments/NavBar.css +++ /dev/null @@ -1,41 +0,0 @@ -.navbar-container { - display: flex; - justify-content: space-between; - align-items: center; - background-color: blue; - width: 100%; - padding: 10px; - box-sizing: border-box; - } - - main { - width: 100%; - } - - .navbar-container img { - width: 40px; - height: 40px; - margin-right: 10px; - } - - .navbar-text { - color: white; - } - - - .help-button { - border: none; - background: none; - - } - - .help-button img { - width: 40px; - height: 40px; - } - - - - - - \ No newline at end of file diff --git a/webapp/src/components/fragments/NavBar.js b/webapp/src/components/fragments/NavBar.js index 2e65ea02..d65ef527 100644 --- a/webapp/src/components/fragments/NavBar.js +++ b/webapp/src/components/fragments/NavBar.js @@ -2,16 +2,25 @@ import React from 'react'; import Typography from "@mui/material/Typography"; import { Link } from 'react-router-dom'; -import "./NavBar.css"; +import "../../custom.css"; +import {useTranslation} from "react-i18next"; function Navbar() { + + const[t, i18n] = useTranslation("global"); + return (
- Know and Win! + {t("navBar.title")} - +
+ + + +
+
); } diff --git a/webapp/src/components/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js similarity index 78% rename from webapp/src/components/AddUser.js rename to webapp/src/components/loginAndRegistration/AddUser.js index f0feb5c0..5911c7f5 100644 --- a/webapp/src/components/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -1,29 +1,31 @@ -// src/components/AddUser.js -import React, { useState } from "react"; +import React from "react"; import { FaUser, FaLock } from "react-icons/fa"; -import "./Login.css"; +import "../../custom.css"; import { Link } from "react-router-dom"; +import { useTranslation } from "react-i18next"; const AddUser = () => { + const { t } = useTranslation("global"); + return (
-

Register

+

{t("addUser.title")}

- +
- +
- +
- +
@@ -33,12 +35,16 @@ const AddUser = () => { }; function LinkLogin() { + const { t } = useTranslation("global"); return ( - Do you have an account? Login here. + {t("addUser.login_link")} ); } + +export default AddUser; + // const [username, setUsername] = useState(''); // const [password, setPassword] = useState(''); // const [error, setError] = useState(''); @@ -90,4 +96,3 @@ function LinkLogin() { // ); // }; -export default AddUser; diff --git a/webapp/src/components/loginAndRegistration/AddUser.test.js b/webapp/src/components/loginAndRegistration/AddUser.test.js new file mode 100644 index 00000000..302db0aa --- /dev/null +++ b/webapp/src/components/loginAndRegistration/AddUser.test.js @@ -0,0 +1,59 @@ +// import React from 'react'; +// import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +// import axios from 'axios'; +// import MockAdapter from 'axios-mock-adapter'; +// import AddUser from './AddUser'; + +// const mockAxios = new MockAdapter(axios); + +// describe('AddUser component', () => { +// beforeEach(() => { +// mockAxios.reset(); +// }); + +// it('should add user successfully', async () => { +// render(); + +// const usernameInput = screen.getByLabelText(/Username/i); +// const passwordInput = screen.getByLabelText(/Password/i); +// const addUserButton = screen.getByRole('button', { name: /Add User/i }); + +// // Mock the axios.post request to simulate a successful response +// mockAxios.onPost('http://localhost:8000/adduser').reply(200); + +// // Simulate user input +// fireEvent.change(usernameInput, { target: { value: 'testUser' } }); +// fireEvent.change(passwordInput, { target: { value: 'testPassword' } }); + +// // Trigger the add user button click +// fireEvent.click(addUserButton); + +// // Wait for the Snackbar to be open +// await waitFor(() => { +// expect(screen.getByText(/User added successfully/i)).toBeInTheDocument(); +// }); +// }); + +// it('should handle error when adding user', async () => { +// render(); + +// const usernameInput = screen.getByLabelText(/Username/i); +// const passwordInput = screen.getByLabelText(/Password/i); +// const addUserButton = screen.getByRole('button', { name: /Add User/i }); + +// // Mock the axios.post request to simulate an error response +// mockAxios.onPost('http://localhost:8000/adduser').reply(500, { error: 'Internal Server Error' }); + +// // Simulate user input +// fireEvent.change(usernameInput, { target: { value: 'testUser' } }); +// fireEvent.change(passwordInput, { target: { value: 'testPassword' } }); + +// // Trigger the add user button click +// fireEvent.click(addUserButton); + +// // Wait for the error Snackbar to be open +// await waitFor(() => { +// expect(screen.getByText(/Error: Internal Server Error/i)).toBeInTheDocument(); +// }); +// }); +// }); diff --git a/webapp/src/components/Login.js b/webapp/src/components/loginAndRegistration/Login.js similarity index 70% rename from webapp/src/components/Login.js rename to webapp/src/components/loginAndRegistration/Login.js index 6a05a02e..167b5ca8 100644 --- a/webapp/src/components/Login.js +++ b/webapp/src/components/loginAndRegistration/Login.js @@ -1,64 +1,35 @@ -// import React, { useState } from "react"; -// import axios from "axios"; +import React from "react"; import { FaUser, FaLock } from "react-icons/fa"; -import "./Login.css"; import { Link } from "react-router-dom"; +import Button from "@mui/material/Button"; +import { useTranslation } from "react-i18next"; +import "../../custom.css"; const Login = () => { + const { t } = useTranslation("global"); - //todo esto de aquí lo dejo por si se necesita en el futuro, es el código base - // const [username, setUsername] = useState(""); - // const [password, setPassword] = useState(""); - // const [error, setError] = useState(""); - // const [loginSuccess, setLoginSuccess] = useState(false); - // const [createdAt, setCreatedAt] = useState(""); - // const [openSnackbar, setOpenSnackbar] = useState(false); - // const apiEndpoint = - // process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000"; - - // const loginUser = async () => { - // try { - // const response = await axios.post(`${apiEndpoint}/login`, { - // username, - // password, - // }); - - // // Extract data from the response - // const { createdAt: userCreatedAt } = response.data; - - // setCreatedAt(userCreatedAt); - // setLoginSuccess(true); - - // setOpenSnackbar(true); - // } catch (error) { - // setError(error.response.data.error); - // } - // }; - - //empieza return (
-

Login

+

{t("login.title")}

- +
- +
- Forgot password? + {t("login.forgot_password")}
- - +
@@ -66,16 +37,27 @@ const Login = () => { ); }; +function ButtonMenu() { + const { t } = useTranslation("global"); + return ( + + + + ); +} function LinkRegister() { + const { t } = useTranslation("global"); return ( - Don't you have an account? Register here. + {t("login.register_link")} ); } + export default Login; + // // src/components/Login.js // import React, { useState } from 'react'; // import axios from 'axios'; diff --git a/webapp/src/components/loginAndRegistration/Login.test.js b/webapp/src/components/loginAndRegistration/Login.test.js new file mode 100644 index 00000000..d95164ca --- /dev/null +++ b/webapp/src/components/loginAndRegistration/Login.test.js @@ -0,0 +1,62 @@ +// import React from 'react'; +// import { render, fireEvent, screen, waitFor, act } from '@testing-library/react'; +// import axios from 'axios'; +// import MockAdapter from 'axios-mock-adapter'; +// import Login from './Login'; + +// const mockAxios = new MockAdapter(axios); + +// describe('Login component', () => { +// beforeEach(() => { +// mockAxios.reset(); +// }); + +// it('should log in successfully', async () => { +// render(); + +// const usernameInput = screen.getByLabelText(/Username/i); +// const passwordInput = screen.getByLabelText(/Password/i); +// const loginButton = screen.getByRole('button', { name: /Login/i }); + +// // Mock the axios.post request to simulate a successful response +// mockAxios.onPost('http://localhost:8000/login').reply(200, { createdAt: '2024-01-01T12:34:56Z' }); + +// // Simulate user input +// await act(async () => { +// fireEvent.change(usernameInput, { target: { value: 'testUser' } }); +// fireEvent.change(passwordInput, { target: { value: 'testPassword' } }); +// fireEvent.click(loginButton); +// }); + +// // Verify that the user information is displayed +// expect(screen.getByText(/Hello testUser!/i)).toBeInTheDocument(); +// expect(screen.getByText(/Your account was created on 1\/1\/2024/i)).toBeInTheDocument(); +// }); + +// it('should handle error when logging in', async () => { +// render(); + +// const usernameInput = screen.getByLabelText(/Username/i); +// const passwordInput = screen.getByLabelText(/Password/i); +// const loginButton = screen.getByRole('button', { name: /Login/i }); + +// // Mock the axios.post request to simulate an error response +// mockAxios.onPost('http://localhost:8000/login').reply(401, { error: 'Unauthorized' }); + +// // Simulate user input +// fireEvent.change(usernameInput, { target: { value: 'testUser' } }); +// fireEvent.change(passwordInput, { target: { value: 'testPassword' } }); + +// // Trigger the login button click +// fireEvent.click(loginButton); + +// // Wait for the error Snackbar to be open +// await waitFor(() => { +// expect(screen.getByText(/Error: Unauthorized/i)).toBeInTheDocument(); +// }); + +// // Verify that the user information is not displayed +// expect(screen.queryByText(/Hello testUser!/i)).toBeNull(); +// expect(screen.queryByText(/Your account was created on/i)).toBeNull(); +// }); +// }); diff --git a/webapp/src/components/questionView/Question.js b/webapp/src/components/questionView/Question.js index 2a0e1425..0249c2f1 100644 --- a/webapp/src/components/questionView/Question.js +++ b/webapp/src/components/questionView/Question.js @@ -1,3 +1,5 @@ +import "../../custom.css"; + class Question{ constructor(json){ this.question = json.question; diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index 153c7906..5a43bf74 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -1,4 +1,5 @@ import Question from './Question'; +import "../../custom.css"; class QuestionGenerator{ constructor(){ diff --git a/webapp/src/components/questionView/QuestionView.css b/webapp/src/components/questionView/QuestionView.css deleted file mode 100644 index d823940f..00000000 --- a/webapp/src/components/questionView/QuestionView.css +++ /dev/null @@ -1,29 +0,0 @@ -button{ - width: 100%; - height: 45px; - background: darkblue; - border: none; - outline: none; - border-radius: 40px; - box-shadow: 0 0 10px black; - cursor:pointer; - font-size: 16px; - color: white; - font-weight: 700; -} - -div { - display: grid; - grid-template-columns: repeat(2, 1fr); /* 2 columns */ - grid-template-rows: repeat(2, 1fr); /* 2 rows */ - gap: 10px; /* Adjust the gap between grid items */ -} - -/* Adjust width of child elements */ -div > * { - width: 100%; /* Adjust as needed */ - margin:1em; -} -p{ - text-align: center; -} \ No newline at end of file diff --git a/webapp/src/components/questionView/QuestionView.js b/webapp/src/components/questionView/QuestionView.js index 2a2be342..b8240d5c 100644 --- a/webapp/src/components/questionView/QuestionView.js +++ b/webapp/src/components/questionView/QuestionView.js @@ -1,12 +1,15 @@ -import Question from './Question'; import QuestionGenerator from './QuestionGenerator'; import { useEffect, useState } from 'react'; -import './QuestionView.css'; +import "../../custom.css"; +import React from "react"; +import Countdown from "react-countdown"; +import {useTranslation} from "react-i18next"; function QuestionView(){ const questionGenerator = new QuestionGenerator(); const [numQuestion, setnumQuestion] = useState(-1); const [questions, setQuestions] = useState([]); + const[t, i18n] = useTranslation("global"); const generateQuestions = async (numQuestion) => { if (numQuestion < 0) { @@ -29,33 +32,46 @@ function QuestionView(){ useEffect(() => {generateQuestions(numQuestion)}, []); return ( -
+
{/*Nav*/} {numQuestion >= 0 ? - : -

Please Wait a bit

} - - + : +

Please Wait a bit...

}
); } -function QuestionComponent({questions, numQuestion, handleClick}){ +function QuestionComponent({questions, numQuestion, handleClick, t}){ + const renderer = ({seconds, completed }) => { + if (completed) { + + return {t("questionView.end_countdown")}; // Rendered when countdown completes + } else { + return {seconds} {t("questionView.seconds")}; // Render countdown + } + }; + return ( <> -

{questions[numQuestion].getQuestion()}

-
- {questions[numQuestion].getAnswers().map((item, index) => ( - - ))} -

Question counter: {numQuestion}

+
+

{questions[numQuestion].getQuestion()}

+
+ +
+
+
+ {questions[numQuestion].getAnswers().map((item, index) => ( + + ))} +
+

{t("questionView.question_counter")} {numQuestion}

); } function Answer({text, onClick}){ return ( - + ); } diff --git a/webapp/src/custom.css b/webapp/src/custom.css new file mode 100644 index 00000000..4964935c --- /dev/null +++ b/webapp/src/custom.css @@ -0,0 +1,375 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; + } + .App { + text-align: center; + } + + .App-logo { + height: 40vmin; + pointer-events: none; + } + + @media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } + } + + .App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; + } + + .App-link { + color: #61dafb; + } + + @keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + .navbar-container { + display: flex; + justify-content: space-between; + align-items: center; + background-color: blue; + width: 100%; + padding: 10px; + box-sizing: border-box; + } + + main { + width: 100%; + } + + .navbar-container img { + display: flex; + justify-content: space-between; + align-items: center; + width: 40px; + height: 40px; + margin-right: 10px; + } + + .navbar-text { + color: white; + } + + + .help-button { + border: none; + background: none; + + } + + .en-button, .es-button{ + margin-right: 10px; + background: rgb(171, 171, 221); + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor:pointer; + font-size: 16px; + color: white; + font-weight: 700; + margin: 0.5em; + } + + .right-nav{ + display: flex; + } + + + + .help-button img { + width: 40px; + height: 40px; + } + + + .menuButton { + width: 40%; + height: 45px; + background: darkblue; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor:pointer; + font-size: 16px; + color: white; + font-weight: 700; + margin: 0.5em; + text-align: center; + } + + .countdown { + font-weight: 500; + margin: 3em; + } + + .topPanel{ + display: flex; + flex-direction: row; + align-items: center; + } + .divMenu { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + } + + .menuButton{ + width: 200px; + height: 45px; + background: darkblue; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor: pointer; + font-size: 16px; + color: white; + font-weight: 700; + margin: 0.5em; + text-decoration: none; + } + + .linkButton { + display: flex; + justify-content: center; + align-items: center; + width: 200px; + height: 45px; + background: darkblue; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor: pointer; + font-size: 16px; + color: white; + font-weight: 700; + margin: 0.5em; + text-decoration: none; +} + + + + + .wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + } + + .wrapper button{ + width: 100%; + height: 45px; + background: darkblue; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor:pointer; + font-size: 16px; + color: white; + font-weight: 700; + } + + .wrapper link{ + font-size: 14.5px; + text-align: center; + margin: 20px 0 15px; + } + + +.wrapper{ + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background-color: lightblue; +} + +.wrapper2{ + width: 420px; + background-color: blue; + color: white; + border-style: 10px; + padding: 30px 40px; + border-radius: 15%; +} + +.wrapper2 h1{ + font-size: 36px; + text-align: center; +} + +.wrapper2 .input-box{ + position: relative; + width: 100%; + height: 50px; + margin: 30px 0; +} + +.input-box input{ + width: 100%; + height: 100%; + background: lightblue; + border: none; + outline: none; + border: 2px solid gray; + border-radius: 40px; + font-size: 14.5px; + color: black; +} + +.input-box input::placeholder{ + background-color: lightblue; +} + +.input-box .icon{ + position: absolute; + right: 20px; + top: 50%; + transform: translateY(-50%); + font-size: 16px; +} + +.wrapper2 .remember-forgot{ + display: flex; + justify-content: space-between; + font-size: 14.5px; + margin: -15px 0 15px; +} + +.remember-forgot label input{ + accent-color: white; + margin-right: 4px; +} + +.remember-forgot a { + color: white; + text-decoration: none; +} + +.remember-forgot a:hover{ + text-decoration: underline; +} + +.wrapper2 button{ + width: 100%; + height: 45px; + background: white; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor:pointer; + font-size: 16px; + color: black; + font-weight: 700; +} + +.button-register { + color: aliceblue; + } + + .button-login { + color: aliceblue; + } + +.register-link p a { + color: white; + text-decoration: none; + font-weight: 600; +} + +.register-link p a:hover{ + text-decoration: underline; +} + +.answerButton { + width: 100%; + height: 45px; + background: darkblue; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor:pointer; + font-size: 16px; + color: white; + font-weight: 700; +} + +.answerPanel { + display: grid; + grid-template-columns: repeat(2, 1fr); /* 2 columns */ + grid-template-rows: repeat(2, 1fr); /* 2 rows */ + gap: 10px; /* Adjust the gap between grid items */ + +} + +/* Adjust width of child elements */ +.answerPanel div > * { + width: 100%; /* Adjust as needed */ + margin:1em; +} + +p{ + text-align: center; +} + + +.question-view-container, .answerPanel { + background-color: lightblue; + padding: 20px; +} + +.Instructions-container { + background-color: #eaf6ff; /* Cambia este valor al color de azul que prefieras */ + padding: 20px; /* Añade un poco de espacio alrededor del contenido */ +} + +.instructions_title{ + background-color: darkblue; + color: white; +} + +.ins_ul p{ + background-color: blue; + color: white; +} + +.Instructions{ + background-color: lightblue; +} + diff --git a/webapp/src/index.css b/webapp/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/webapp/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/webapp/src/index.js b/webapp/src/index.js index d563c0fb..f5d8ebec 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -1,13 +1,37 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; +import './custom.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import {I18nextProvider} from "react-i18next"; +import i18next from "i18next"; +import global_es from "./translations/es/global.json" +import global_en from "./translations/en/global.json" + + +i18next.init({ + interpolation: {escapeValue: false}, + + // idioma con el que va a empezar la app + lng: "en", + + resources: { + es:{ + global:global_es, + }, + en:{ + global:global_en, + } + }, +}) const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - + {/* envolviendo la app con el sistema de traducciones */} + + + ); diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json new file mode 100644 index 00000000..b7f7248d --- /dev/null +++ b/webapp/src/translations/en/global.json @@ -0,0 +1,61 @@ +{ + "home": { + "welcome": "Welcome to WIQ!", + "how_to_play": "How to play?", + "login": "Login", + "register": "Don't have an account? Register here." + }, + "navBar": { + "title": "Know and win!" + }, + "instructions": { + "title": "WIQ Instructions", + "objective": "Objective:", + "objective_p1": "The objective of the game is to answer as many questions correctly as possible.", + "how_to_play": "How to Play:", + "how_to_play_p1": "The game consists of a series of questions.", + "how_to_play_p2": "Read each question carefully.", + "how_to_play_p3": "Choose the correct answer from the options provided.", + "how_to_play_p4": "Click or tap on your selected answer to submit it.", + "scoring": "Scoring:", + "scoring_p1": "Each correct answer earns you x points.", + "scoring_p2": "Incorrect answers do not deduct points.", + "time_limit": "Time Limit:", + "time_limit_p1": "Some game modes may have a time limit for answering each question. Be quick and accurate to maximize your score.", + "have_fun": "Have Fun!:", + "have_fun_p1": "Enjoy the game and test your knowledge. Good luck!" + }, + "login": { + "title": "Login", + "username_placeholder": "Username", + "password_placeholder": "Password", + "remember_me": "Remember me", + "forgot_password": "Forgot password?", + "login_button": "Login", + "register_link": "Don't you have an account? Register here." + }, + "addUser": { + "title": "Register", + "username_placeholder": "Username", + "password_placeholder": "Password", + "repeat_password_placeholder": "Repeat password", + "register_button": "Register", + "login_link": "Do you have an account? Login here." + }, + "gameMenu":{ + "history_button":"View Historical Data", + "new_game_button":"Create New Game", + "title":"Game Menu" + }, + "questionView":{ + "seconds":"seconds", + "question_counter":"Question counter :", + "end_countdown":"Time's up!" + } +} + + + + + + \ No newline at end of file diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json new file mode 100644 index 00000000..3277d13a --- /dev/null +++ b/webapp/src/translations/es/global.json @@ -0,0 +1,61 @@ +{ + "home": { + "welcome": "¡Bienvenido a WIQ!", + "how_to_play": "¿Cómo jugar?", + "login": "Iniciar sesión", + "register": "¿No tienes una cuenta? Regístrate aquí." + }, + + "navBar": { + "title": "¡Saber y ganar!" + }, + + "instructions": { + "title": "Instrucciones de WIQ", + "objective": "Objetivo:", + "objective_p1": "El objetivo del juego es responder tantas preguntas correctamente como sea posible.", + "how_to_play": "Cómo Jugar:", + "how_to_play_p1": "El juego consiste en una serie de preguntas.", + "how_to_play_p2": "Lee cada pregunta cuidadosamente.", + "how_to_play_p3": "Elige la respuesta correcta de las opciones proporcionadas.", + "how_to_play_p4": "Haz clic o toca tu respuesta seleccionada para enviarla.", + + "scoring": "Puntuación:", + "scoring_p1": "Cada respuesta correcta te otorga x puntos.", + "scoring_p2": "Las respuestas incorrectas no restan puntos.", + "time_limit": "Límite de Tiempo:", + "time_limit_p1": "Algunos modos de juego pueden tener un límite de tiempo para responder cada pregunta. Sé rápido y preciso para maximizar tu puntuación.", + "have_fun": "¡Diviértete!", + "have_fun_p1": "¡Disfruta del juego y pon a prueba tus conocimientos. Buena suerte!" + }, + "login": { + "title": "Inicio de sesión", + "username_placeholder": "Nombre de usuario", + "password_placeholder": "Contraseña", + "remember_me": "Recordarme", + "forgot_password": "¿Olvidaste tu contraseña?", + "login_button": "Iniciar sesión", + "register_link": "¿No tienes una cuenta? Regístrate aquí." + }, + + "addUser": { + "title": "Registrar", + "username_placeholder": "Nombre de usuario", + "password_placeholder": "Contraseña", + "repeat_password_placeholder": "Repetir contraseña", + "register_button": "Registrarse", + "login_link": "¿Ya tienes una cuenta? Inicia sesión aquí." + }, + "gameMenu":{ + "history_button":"Ver Historial", + "new_game_button":"Crear nuevo juego", + "title":"Menú del Juego" + },"questionView":{ + "seconds":"segundos", + "question_counter":"Número de pregunta : ", + "end_countdown":"¡Se acabó el tiempo!" + } + + + } + \ No newline at end of file