diff --git a/.eslintrc.json b/.eslintrc.json index c9c0675c..81138c66 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -42,6 +42,10 @@ "allowSingleLine": true } ], + "linebreak-style":[ + "off", + "unix" + ], "comma-dangle": [ "error", "never" diff --git a/README.md b/README.md index d1f22b80..e23ae128 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ # Redux quiz group project -Replace this readme with your own information about your project. +The task was to build a quiz game using React, Redux and styled components. -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. ## The problem -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? +With the group we started planning how the application would look (pages, resuable styled components etc), visualized it with a sketch and planned the steps of the implementation. +We first used mob programming approach to create the basic logic using Redux, then worked on separate branches to style the pages and position the components. ## View it live -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +Deployed link: https://food-quiz-by-yu-joanna-nora.netlify.app/ + +## Image credits +German Flag - Photo by engin akyurt on Unsplash + +Paella - Photo by Douglas Lopez on Unsplash + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8bd1eecb..af65dde9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,20 +9,25 @@ "version": "1.0.0", "dependencies": { "@babel/eslint-parser": "^7.18.9", - "@reduxjs/toolkit": "^1.8.3", + "@reduxjs/toolkit": "^1.9.3", "eslint": "^8.21.0", "eslint-config-airbnb": "^19.0.4", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", + "rc-progress": "^3.4.1", "react": "^18.2.0", + "react-accessible-accordion": "^5.0.0", + "react-countdown": "^2.3.5", "react-dom": "^18.2.0", + "react-icons": "^4.8.0", + "react-moving-text": "^0.0.7", "react-redux": "^8.0.2", - "react-scripts": "^5.0.1" + "styled-components": "^5.3.9" }, "devDependencies": { - "react-scripts": "5.0.1" + "react-scripts": "^5.0.1" } }, "node_modules/@ampproject/remapping": { @@ -132,7 +137,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dev": true, "dependencies": { "@babel/types": "^7.18.6" }, @@ -2156,6 +2160,29 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/@eslint/eslintrc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", @@ -3128,14 +3155,14 @@ } }, "node_modules/@reduxjs/toolkit": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz", - "integrity": "sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", + "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", "dependencies": { - "immer": "^9.0.7", - "redux": "^4.1.2", - "redux-thunk": "^2.4.1", - "reselect": "^4.1.5" + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", @@ -4927,6 +4954,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.1.tgz", + "integrity": "sha512-c8lJlszObVQPguHkI+akXv8+Jgb9Ccujx0EetL7oIvwU100LxO6XAGe45qry37wUL40a5U9f23SYrivro2XKhA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.21", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -5264,6 +5311,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5388,6 +5443,11 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/clean-css": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", @@ -5751,6 +5811,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", @@ -5959,6 +6027,16 @@ "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", "dev": true }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -8873,9 +8951,9 @@ } }, "node_modules/immer": { - "version": "9.0.15", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz", - "integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==", + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -11767,8 +11845,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -14012,8 +14089,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -14270,6 +14346,33 @@ "node": ">=0.10.0" } }, + "node_modules/rc-progress": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.4.1.tgz", + "integrity": "sha512-eAFDHXlk8aWpoXl0llrenPMt9qKHQXphxcVsnKs0FHC6eCSk1ebJtyaVjJUzKe0233ogiLDeEFK1Uihz3s67hw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.29.3", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.29.3.tgz", + "integrity": "sha512-wX6ZwQTzY2v7phJBquN4mSEIFR0E0qumlENx0zjENtDvoVSq2s7cR95UidKRO1hOHfDsecsfM9D1gO4Kebs7fA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -14281,6 +14384,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-accessible-accordion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-accessible-accordion/-/react-accessible-accordion-5.0.0.tgz", + "integrity": "sha512-MT2obYpTgLIIfPr9d7hEyvPB5rg8uJcHpgA83JSRlEUHvzH48+8HJPvzSs+nM+XprTugDgLfhozO5qyJpBvYRQ==", + "peerDependencies": { + "react": "^16.3.2 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.3.3 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-app-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", @@ -14298,6 +14410,18 @@ "node": ">=14" } }, + "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", @@ -14442,11 +14566,27 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "dev": true }, + "node_modules/react-icons": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", + "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-moving-text": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/react-moving-text/-/react-moving-text-0.0.7.tgz", + "integrity": "sha512-WA72if8QS2/QVOmZ2mDnWu1UFkMqt7Y1TFLfKT3MDRwPbcpz2F7WAyGi349anuKth0bQKg0CQddlQLQQiII8zQ==", + "peerDependencies": { + "styled-components": ">= 4" + } + }, "node_modules/react-redux": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz", @@ -14655,9 +14795,9 @@ } }, "node_modules/redux-thunk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", "peerDependencies": { "redux": "^4" } @@ -14818,9 +14958,9 @@ "dev": true }, "node_modules/reselect": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz", - "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==" + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" }, "node_modules/resolve": { "version": "1.22.1", @@ -15342,6 +15482,11 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15751,6 +15896,35 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", + "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, "node_modules/stylehacks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", @@ -17604,7 +17778,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "dev": true, "requires": { "@babel/types": "^7.18.6" } @@ -18936,6 +19109,29 @@ "dev": true, "requires": {} }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint/eslintrc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", @@ -19654,14 +19850,14 @@ } }, "@reduxjs/toolkit": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz", - "integrity": "sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", + "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", "requires": { - "immer": "^9.0.7", - "redux": "^4.1.2", - "redux-thunk": "^2.4.1", - "reselect": "^4.1.5" + "immer": "^9.0.16", + "redux": "^4.2.0", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.7" } }, "@rollup/plugin-babel": { @@ -21020,6 +21216,23 @@ "@babel/helper-define-polyfill-provider": "^0.3.2" } }, + "babel-plugin-styled-components": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.1.tgz", + "integrity": "sha512-c8lJlszObVQPguHkI+akXv8+Jgb9Ccujx0EetL7oIvwU100LxO6XAGe45qry37wUL40a5U9f23SYrivro2XKhA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.21", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -21288,6 +21501,11 @@ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "dev": true }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -21378,6 +21596,11 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "clean-css": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", @@ -21660,6 +21883,11 @@ "postcss-selector-parser": "^6.0.9" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-declaration-sorter": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", @@ -21790,6 +22018,16 @@ "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", "dev": true }, + "css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -23941,9 +24179,9 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" }, "immer": { - "version": "9.0.15", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz", - "integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==" + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" }, "import-fresh": { "version": "3.3.0", @@ -26075,8 +26313,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.debounce": { "version": "4.0.8", @@ -27550,8 +27787,7 @@ "postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "prelude-ls": { "version": "1.2.1", @@ -27742,6 +27978,25 @@ } } }, + "rc-progress": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.4.1.tgz", + "integrity": "sha512-eAFDHXlk8aWpoXl0llrenPMt9qKHQXphxcVsnKs0FHC6eCSk1ebJtyaVjJUzKe0233ogiLDeEFK1Uihz3s67hw==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + } + }, + "rc-util": { + "version": "5.29.3", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.29.3.tgz", + "integrity": "sha512-wX6ZwQTzY2v7phJBquN4mSEIFR0E0qumlENx0zjENtDvoVSq2s7cR95UidKRO1hOHfDsecsfM9D1gO4Kebs7fA==", + "requires": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0" + } + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -27750,6 +28005,12 @@ "loose-envify": "^1.1.0" } }, + "react-accessible-accordion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-accessible-accordion/-/react-accessible-accordion-5.0.0.tgz", + "integrity": "sha512-MT2obYpTgLIIfPr9d7hEyvPB5rg8uJcHpgA83JSRlEUHvzH48+8HJPvzSs+nM+XprTugDgLfhozO5qyJpBvYRQ==", + "requires": {} + }, "react-app-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", @@ -27764,6 +28025,14 @@ "whatwg-fetch": "^3.6.2" } }, + "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==", + "requires": { + "prop-types": "^15.7.2" + } + }, "react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -27874,11 +28143,23 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "dev": true }, + "react-icons": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", + "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-moving-text": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/react-moving-text/-/react-moving-text-0.0.7.tgz", + "integrity": "sha512-WA72if8QS2/QVOmZ2mDnWu1UFkMqt7Y1TFLfKT3MDRwPbcpz2F7WAyGi349anuKth0bQKg0CQddlQLQQiII8zQ==", + "requires": {} + }, "react-redux": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz", @@ -28030,9 +28311,9 @@ } }, "redux-thunk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", "requires": {} }, "regenerate": { @@ -28160,9 +28441,9 @@ "dev": true }, "reselect": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz", - "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==" + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", + "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" }, "resolve": { "version": "1.22.1", @@ -28546,6 +28827,11 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -28860,6 +29146,23 @@ "dev": true, "requires": {} }, + "styled-components": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", + "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "stylehacks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", diff --git a/package.json b/package.json index 58da74eb..057ecda2 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,22 @@ "private": true, "dependencies": { "@babel/eslint-parser": "^7.18.9", - "@reduxjs/toolkit": "^1.8.3", + "@reduxjs/toolkit": "^1.9.3", "eslint": "^8.21.0", "eslint-config-airbnb": "^19.0.4", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", + "rc-progress": "^3.4.1", "react": "^18.2.0", + "react-accessible-accordion": "^5.0.0", + "react-countdown": "^2.3.5", "react-dom": "^18.2.0", + "react-icons": "^4.8.0", + "react-moving-text": "^0.0.7", "react-redux": "^8.0.2", - "react-scripts": "^5.0.1" + "styled-components": "^5.3.9" }, "scripts": { "start": "react-scripts start", @@ -36,6 +41,6 @@ ] }, "devDependencies": { - "react-scripts": "5.0.1" + "react-scripts": "^5.0.1" } } diff --git a/public/images/logo.jpg b/public/images/logo.jpg new file mode 100644 index 00000000..5d3ff167 Binary files /dev/null and b/public/images/logo.jpg differ diff --git a/public/images/option1.png b/public/images/option1.png new file mode 100644 index 00000000..d8be6b2b Binary files /dev/null and b/public/images/option1.png differ diff --git a/public/images/option2.png b/public/images/option2.png new file mode 100644 index 00000000..710af64f Binary files /dev/null and b/public/images/option2.png differ diff --git a/public/images/option3.png b/public/images/option3.png new file mode 100644 index 00000000..da906660 Binary files /dev/null and b/public/images/option3.png differ diff --git a/public/images/option4.png b/public/images/option4.png new file mode 100644 index 00000000..fde1c891 Binary files /dev/null and b/public/images/option4.png differ diff --git a/public/images/questionImg1.jpg b/public/images/questionImg1.jpg new file mode 100644 index 00000000..aa791a08 Binary files /dev/null and b/public/images/questionImg1.jpg differ diff --git a/public/images/questionImg2.jpg b/public/images/questionImg2.jpg new file mode 100644 index 00000000..9e9cd9e9 Binary files /dev/null and b/public/images/questionImg2.jpg differ diff --git a/public/images/questionImg3.jpg b/public/images/questionImg3.jpg new file mode 100644 index 00000000..61db5380 Binary files /dev/null and b/public/images/questionImg3.jpg differ diff --git a/public/images/questionImg4.jpg b/public/images/questionImg4.jpg new file mode 100644 index 00000000..ffb99bf4 Binary files /dev/null and b/public/images/questionImg4.jpg differ diff --git a/public/images/questionImg5.jpg b/public/images/questionImg5.jpg new file mode 100644 index 00000000..0cd68bf5 Binary files /dev/null and b/public/images/questionImg5.jpg differ diff --git a/public/images/questionImg6.jpg b/public/images/questionImg6.jpg new file mode 100644 index 00000000..bf3c71f9 Binary files /dev/null and b/public/images/questionImg6.jpg differ diff --git a/public/images/questionImg7.jpg b/public/images/questionImg7.jpg new file mode 100644 index 00000000..f8bf768c Binary files /dev/null and b/public/images/questionImg7.jpg differ diff --git a/public/images/questionImg8.jpg b/public/images/questionImg8.jpg new file mode 100644 index 00000000..78f269e0 Binary files /dev/null and b/public/images/questionImg8.jpg differ diff --git a/public/images/questionImg9.jpg b/public/images/questionImg9.jpg new file mode 100644 index 00000000..6958c45f Binary files /dev/null and b/public/images/questionImg9.jpg differ diff --git a/public/index.html b/public/index.html index e6730aa6..074cd4e7 100644 --- a/public/index.html +++ b/public/index.html @@ -1,10 +1,10 @@ - - - - - Technigo React App - + + + + + Quiz! + - - -
- - + - + \ No newline at end of file diff --git a/src/App.js b/src/App.js index 690bd373..879d7ebd 100644 --- a/src/App.js +++ b/src/App.js @@ -3,7 +3,8 @@ import { Provider } from 'react-redux'; import { combineReducers, configureStore } from '@reduxjs/toolkit'; import { quiz } from 'reducers/quiz'; -import { CurrentQuestion } from 'components/CurrentQuestion'; +import { Main } from 'components/Main'; +import { GlobalStyle } from 'components/GlobalStyle'; const reducer = combineReducers({ quiz: quiz.reducer @@ -14,7 +15,8 @@ const store = configureStore({ reducer }); export const App = () => { return ( - + +
); -} +} \ No newline at end of file diff --git a/src/components/CurrentQuestion.js b/src/components/CurrentQuestion.js deleted file mode 100644 index 36ee2224..00000000 --- a/src/components/CurrentQuestion.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { useSelector } from 'react-redux' - -export const CurrentQuestion = () => { - const question = useSelector((state) => state.quiz.questions[state.quiz.currentQuestionIndex]) - - if (!question) { - return

Oh no! I could not find the current question!

- } - - return ( -
-

Question: {question.questionText}

-
- ) -} diff --git a/src/components/CurrentQuestion/CurrentQuestion.js b/src/components/CurrentQuestion/CurrentQuestion.js new file mode 100644 index 00000000..95748624 --- /dev/null +++ b/src/components/CurrentQuestion/CurrentQuestion.js @@ -0,0 +1,78 @@ + +/* eslint-disable max-len */ +import React, { useState, useRef } from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { quiz } from 'reducers/quiz' +import { Timer } from 'components/Timer'; +import { Title } from 'reusable-components/Title' +import { Button, ButtonContainer } from 'reusable-components/Button' +import { Image } from 'reusable-components/Image' +import { CurrentQuestionContainer, ImgBox, OptionsImage } from './CurrentQuestionStyling' +import { ProgressBar } from '../ProgressBar'; + +export const CurrentQuestion = (props) => { + const question = useSelector((store) => store.quiz.questions[store.quiz.currentQuestionIndex]) + const btnColor = useSelector((store) => store.quiz.btnColor) + const correctAnswerIndex = useSelector((store) => store.quiz.questions[store.quiz.currentQuestionIndex].correctAnswerIndex) + const { setScore, score } = props + const [selectedAnswerIndex, setSelectedAnswerIndex] = useState('') + const correctAnswerIndicator = useSelector((store) => store.quiz.correctAnswerIndicator) + const dispatch = useDispatch() + const countdownRef = useRef(null) + const disabledButtons = useSelector((store) => store.quiz.disabledButtons) + + const onAnswerSubmit = (questionId, answerIndex) => { + if (correctAnswerIndex === answerIndex) { + setScore(score + 3) + } else { + setScore(score - 2) + } + setSelectedAnswerIndex(answerIndex) + dispatch(quiz.actions.submitAnswer({ questionId, answerIndex })) + setTimeout(() => dispatch(quiz.actions.goToNextQuestion()), 1500) + countdownRef.current.stop(); + setTimeout(() => countdownRef.current.start(), 1500) + } + + const buttonStyle = (answerIndex) => { + if (selectedAnswerIndex === answerIndex) { + return { backgroundColor: btnColor } + } + if (correctAnswerIndicator) { + if (answerIndex === correctAnswerIndex) { + return { backgroundColor: '#56ab2f' } + } + } + } + if (!question) { + return

Oh no! I could not find the current question!

+ } + + return ( + + + + img + + + Question: {question.questionText} + + {question.options.map((answer, index) => { + return ( + + ) + })} + + + + ) +} diff --git a/src/components/CurrentQuestion/CurrentQuestionStyling.js b/src/components/CurrentQuestion/CurrentQuestionStyling.js new file mode 100644 index 00000000..46beb4c4 --- /dev/null +++ b/src/components/CurrentQuestion/CurrentQuestionStyling.js @@ -0,0 +1,51 @@ +import styled from 'styled-components'; + +export const CurrentQuestionContainer = styled.div` +background: linear-gradient(to right, #6E48AA, #9D50BB); +min-height: 100vh; +width: 100vw; +display: flex; +flex-direction: column; +justify-content: center; +align-items: center; +gap:2rem; +box-sizing:border-box; +padding:1em; +font-size:1.2em; + +@media (min-width: 1024px) { +display: flex; +width: 100vw; + +} +` +export const ImgBox = styled.div` +max-width: 90vw; +display: flex; +flex-direction: column; +justify-content: center; +align-items: center; +padding: 0 5%; +gap:1rem; +` +export const ProgressBarContainer = styled.div` +width: 100%; +display:flex; +align-items:center; +justify-content:center; +` +export const CountdownBox = styled.div` +border: 7px solid rgba(181, 118, 221, 0.65); +border-radius:50%; +padding:4px; +margin:5px; + +@media (min-width: 1024px) { +display: flex; +width: 2em; +height: 2em; +} +` +export const OptionsImage = styled.img` +width:90%; +` diff --git a/src/components/GlobalStyle.js b/src/components/GlobalStyle.js new file mode 100644 index 00000000..5ba9eb38 --- /dev/null +++ b/src/components/GlobalStyle.js @@ -0,0 +1,8 @@ +import { createGlobalStyle } from 'styled-components'; + +export const GlobalStyle = createGlobalStyle` + body { + font-family: 'Open Sans', sans-serif; + font-size: 16px; + } +`; \ No newline at end of file diff --git a/src/components/Main.js b/src/components/Main.js new file mode 100644 index 00000000..44df73e0 --- /dev/null +++ b/src/components/Main.js @@ -0,0 +1,19 @@ +import React, { useState } from 'react' +import { useSelector } from 'react-redux' +import { CurrentQuestion } from './CurrentQuestion/CurrentQuestion' +import { SummaryPage } from './SummaryPage/SummaryPage' +import { StartPage } from './StartPage/StartPage' + +export const Main = () => { + const [score, setScore] = useState(0) + const showResults = useSelector((ReduxStore) => ReduxStore.quiz.quizOver) + return ( +
+ {showResults === null && ()} + {showResults !== null && !showResults && ( + + )} + {showResults !== null && showResults && ()} +
+ ) +} \ No newline at end of file diff --git a/src/components/ProgressBar.js b/src/components/ProgressBar.js new file mode 100644 index 00000000..c5181c8a --- /dev/null +++ b/src/components/ProgressBar.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Line } from 'rc-progress'; + +import { useSelector } from 'react-redux' + +export const ProgressBar = () => { + // eslint-disable-next-line max-len + const currentQuestionId = useSelector((store) => store.quiz.questions[store.quiz.currentQuestionIndex].id) + const totalQuestions = useSelector((store) => store.quiz.questions.length) + + return ( + + ) +} diff --git a/src/components/StartPage/StartPage.js b/src/components/StartPage/StartPage.js new file mode 100644 index 00000000..4081f056 --- /dev/null +++ b/src/components/StartPage/StartPage.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { useDispatch } from 'react-redux'; +import { quiz } from 'reducers/quiz'; +import { ButtonContainer, Button } from 'reusable-components/Button'; +import { Title } from 'reusable-components/Title'; +import { StartContainer, Logo, InfoContainer } from './StartPageStyling'; + +export const StartPage = () => { + const dispatch = useDispatch() + const startOnClick = () => { + dispatch(quiz.actions.startQuiz()) + } + return ( + + + + {/* Quiz! */} + + See how much you know about food around the world. + Correct: +3 + Incorrect: -2 + + + + + + ) +} \ No newline at end of file diff --git a/src/components/StartPage/StartPageStyling.js b/src/components/StartPage/StartPageStyling.js new file mode 100644 index 00000000..f1981b10 --- /dev/null +++ b/src/components/StartPage/StartPageStyling.js @@ -0,0 +1,47 @@ +import styled from 'styled-components'; + +export const StartContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + max-width: 100vw; + height:100vh; + color: #dfe3f5; + background: #9D50BB; /* fallback for old browsers */ + background: -webkit-linear-gradient(to right, #6E48AA, #9D50BB); /* Chrome 10-25, Safari 5.1-6 */ + background: linear-gradient(to right, #6E48AA, #9D50BB); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ + gap:2em; +` + +export const Logo = styled.img` + display:flex; + width:30%; + border-radius: 50%; + margin-bottom:1rem; + + +@media (min-width: 1024px) { + width: 15%; +} +` + +export const InfoContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content:center; + width: 70vw; + gap:1.5em; + border:3px solid rgba(223, 227, 245, 0.4); + border-radius: 10px; + padding:1rem; + margin-top: 1rem; + color:black; + background-color: rgba(223, 227, 245, 0.4); + + @media (min-width: 1024px) { + max-width:fit-content; + +} +` diff --git a/src/components/SummaryPage/AccordionComponent.js b/src/components/SummaryPage/AccordionComponent.js new file mode 100644 index 00000000..bbe61110 --- /dev/null +++ b/src/components/SummaryPage/AccordionComponent.js @@ -0,0 +1,50 @@ +/* eslint-disable max-len */ +import React from 'react' +import { useSelector } from 'react-redux' +import './accordion.css'; +import { + Accordion, + AccordionItem, + AccordionItemHeading, + AccordionItemButton, + AccordionItemPanel +} from 'react-accessible-accordion'; +import { AnswerImg, AnswerText, Container, ContainerRow } from './SummaryPageStyling'; + +export const AccordionComponent = () => { + const answers = useSelector((store) => store.quiz.answers) + const wrongAnswers = answers.filter((answer) => answer.isCorrect === false) + console.log(wrongAnswers) + const countOfWrongAnswers = wrongAnswers.length + console.log(countOfWrongAnswers) + return ( + + {wrongAnswers.map(((wrongAnswer) => { + return ( + + + {wrongAnswer.question.questionText} + + + + + Your answer: + {wrongAnswer.answer.includes('.png') + ? + : {wrongAnswer.answer} } + + + Correct answer: + { + wrongAnswer.question.options[wrongAnswer.question.correctAnswerIndex].includes('.png') + ? : {wrongAnswer.question.options[wrongAnswer.question.correctAnswerIndex]} + } + + + + + ) + }))} + + ) +} \ No newline at end of file diff --git a/src/components/SummaryPage/SummaryPage.js b/src/components/SummaryPage/SummaryPage.js new file mode 100644 index 00000000..a0056d0a --- /dev/null +++ b/src/components/SummaryPage/SummaryPage.js @@ -0,0 +1,47 @@ +import React from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { MovingComponent } from 'react-moving-text' +import { quiz } from 'reducers/quiz'; +import { TfiAlarmClock } from 'react-icons/tfi'; +import { Button } from 'reusable-components/Button' +import { Title } from 'reusable-components/Title'; +import { Container, ContainerRow, Question } from './SummaryPageStyling'; +import { AccordionComponent } from './AccordionComponent'; + +export const SummaryPage = ({ score, setScore }) => { + const answers = useSelector((store) => store.quiz.answers) + const wrongAnswers = answers.filter((answer) => answer.isCorrect === false) + const startTime = useSelector((store) => store.quiz.startTime) + const countOfWrongAnswers = wrongAnswers.length + + const endTime = new Date().getTime() + const seconds = (endTime - startTime) / 1000; + + const dispatch = useDispatch() + const restartQuiz = () => { + setScore(0) + dispatch(quiz.actions.restart()) + } + + return ( + + + <MovingComponent + type="zoomIn" + duration="2000ms" + delay="0s" + direction="normal" + timing="ease-out" + iteration="2" + fillMode="none"> + Your score is {score > 0 ? score : 0}/27! + </MovingComponent> + +

Time spent: {Math.floor(seconds)}s

+ You had {countOfWrongAnswers} incorrect answers + + +
+ ) +} \ No newline at end of file diff --git a/src/components/SummaryPage/SummaryPageStyling.js b/src/components/SummaryPage/SummaryPageStyling.js new file mode 100644 index 00000000..b45b76dc --- /dev/null +++ b/src/components/SummaryPage/SummaryPageStyling.js @@ -0,0 +1,45 @@ +import styled from 'styled-components'; + +export const Container = styled.section` +background: #9D50BB; /* fallback for old browsers */ +background: -webkit-linear-gradient(to right, #6E48AA, #9D50BB); /* Chrome 10-25, Safari 5.1-6 */ +background: linear-gradient(to right, #6E48AA, #9D50BB); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ +display:flex; +align-items:center; +justify-content:center; +flex-direction:column; +min-height:${(props) => (props.main ? '100vh' : '100%')}; +color: #dfe3f5; +gap:0.7rem; +padding:1rem; + + @media (min-width: 768px) { + font-size:110%; + } + @media (min-width: 1024px) { + font-size:120%; + } +` +export const ContainerRow = styled.section` +display:flex; +align-items:center; +justify-content:center; +gap:0.2rem; +` + +export const AnswerText = styled.p` +font-size: 0.8em; +color:${(props) => (props.wrong ? '#dfe3f5' : '#5ADF1D')}; +overflow-wrap:break-word; +` + +export const AnswerImg = styled.img` +height: 1.2rem; +` + +export const Question = styled.h1` +font-size:1em; + color:#dfe3f5; + overflow-wrap:break-word; + text-align:center; +` diff --git a/src/components/SummaryPage/accordion.css b/src/components/SummaryPage/accordion.css new file mode 100644 index 00000000..8e24a925 --- /dev/null +++ b/src/components/SummaryPage/accordion.css @@ -0,0 +1,72 @@ +.accordion { + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 2px; + max-width: 500px; +} + +.accordion__item+.accordion__item { + border-top: 1px solid rgba(0, 0, 0, 0.1); + width: 100%; +} + +.accordion__button { + background: #9D50BB; + /* fallback for old browsers */ + background: -webkit-linear-gradient(to right, #6E48AA, #9D50BB); + /* Chrome 10-25, Safari 5.1-6 */ + background: linear-gradient(to right, #6E48AA, #9D50BB); + /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ + color: #dfe3f5; + cursor: pointer; + padding: 18px; + width: 100%; + text-align: left; + border: none; + box-sizing: border-box; + font-size: 0.95em; +} + +.accordion__button:hover { + background-color: #ddd; +} + +.accordion__button:before { + display: inline-block; + content: ''; + height: 10px; + width: 10px; + margin-right: 12px; + border-bottom: 2px solid currentColor; + border-right: 2px solid currentColor; + transform: rotate(-45deg); +} + +.accordion__button[aria-expanded='true']::before, +.accordion__button[aria-selected='true']::before { + transform: rotate(45deg); +} + +[hidden] { + display: none; +} + +.accordion__panel { + padding: 20px; + animation: fadein 0.35s ease-in; + width: 100%; + box-sizing: border-box; +} + +/* -------------------------------------------------- */ +/* ---------------- Animation part ------------------ */ +/* -------------------------------------------------- */ + +@keyframes fadein { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} \ No newline at end of file diff --git a/src/components/Timer.js b/src/components/Timer.js new file mode 100644 index 00000000..6097530b --- /dev/null +++ b/src/components/Timer.js @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import Countdown from 'react-countdown'; +import { useDispatch, useSelector } from 'react-redux' +import { quiz } from 'reducers/quiz'; +import { Title } from 'reusable-components/Title' +import { CountdownBox } from './CurrentQuestion/CurrentQuestionStyling' + +export const Timer = ({ countdownRef, setScore, score }) => { + const currentQuestionId = useSelector( + (store) => store.quiz.questions[store.quiz.currentQuestionIndex].id + ) + const dispatch = useDispatch() + const [expirationTime, setExpirationTime] = useState(Date.now() + 10000); + + const renderer = ({ seconds, completed }) => { + if (completed) { + return 00; + } else { + return {seconds.toString().padStart(2, '0')}; + } + }; + + const handleComplete = (questionId, answerIndex) => { + setExpirationTime(Date.now() + 10000); + console.log('Countdown completed'); + console.log(currentQuestionId) + setScore(score - 2) + dispatch(quiz.actions.submitAnswer({ questionId, answerIndex })) + dispatch(quiz.actions.goToNextQuestion()) + countdownRef.current.stop(); + countdownRef.current.start(); + }; + + return ( + + + <Countdown + zeroPadTime={2} + renderer={renderer} + ref={countdownRef} + date={expirationTime} + onComplete={() => { + handleComplete(currentQuestionId, null); + }} /> + + + ) +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 4a1df4db..ccd871b2 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,10 @@ +html, +#root, +body, +#root>div { + min-height: 100%; +} + body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", @@ -11,3 +18,22 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + + +.green { + background: #56ab2f; + /* fallback for old browsers */ + background: -webkit-linear-gradient(to right, #a8e063, #56ab2f); + /* Chrome 10-25, Safari 5.1-6 */ + background: linear-gradient(to right, #a8e063, #56ab2f); + /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ +} + +.red { + background: #FF416C; + /* fallback for old browsers */ + background: -webkit-linear-gradient(to right, #FF4B2B, #FF416C); + /* Chrome 10-25, Safari 5.1-6 */ + background: linear-gradient(to right, #FF4B2B, #FF416C); + /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ +} \ No newline at end of file diff --git a/src/reducers/quiz.js b/src/reducers/quiz.js index a38bbf68..b880d22d 100644 --- a/src/reducers/quiz.js +++ b/src/reducers/quiz.js @@ -1,16 +1,72 @@ + import { createSlice } from '@reduxjs/toolkit' +// import { TfiTime } from 'react-icons/tfi' -// Change these to your own questions! const questions = [ - { id: 1, questionText: 'Who set the Olympic record for the 100m dash in 2012?', options: ['Usain Bolt', 'Justin Gatlin', 'Tyson Gay', 'Asafa Powell'], correctAnswerIndex: 0 }, - { id: 2, questionText: 'When was Michael Phelps last named male World Swimmer of the Year?', options: ['2012', '2014', '2016', '2018'], correctAnswerIndex: 2 } + { id: 1, + img: '/images/questionImg1.jpg', + questionText: 'What is Germany said to have invented?', + options: ['/images/option1.png', '/images/option2.png', '/images/option3.png', '/images/option4.png'], + correctAnswerIndex: 0 }, + + { id: 2, + img: '/images/questionImg2.jpg', + questionText: 'What is the common name for dried plums?', + options: ['Prunes', 'Grapes', 'Raisins', 'Apricots'], + correctAnswerIndex: 0 }, + + { id: 3, + img: '/images/questionImg3.jpg', + questionText: 'What name does deer meat go by?', + options: ['Squab', 'Veal', 'Venison', 'Mutton'], + correctAnswerIndex: 2 }, + + { id: 4, + img: '/images/questionImg4.jpg', + questionText: 'What other name does “corn” go by?', + options: ['Yellow rice', 'Popcorn', 'Barley', 'Maize'], + correctAnswerIndex: 3 }, + + { id: 5, + img: '/images/questionImg5.jpg', + questionText: 'What is the primary ingredient in hummus?', + options: ['Corn', 'Chickpeas', 'Lentils', 'Cabbage'], + correctAnswerIndex: 1 }, + + { id: 6, + img: '/images/questionImg6.jpg', + questionText: 'Which country produces the most coffee in the world?', + options: ['Brazil', 'Colombia', 'Argentina', 'Peru'], + correctAnswerIndex: 0 }, + + { id: 7, + img: '/images/questionImg7.jpg', + questionText: 'What is the name of buckwheat noodles?', + options: ['Ramen', 'Somen', 'Soba', 'Udon'], + correctAnswerIndex: 2 }, + + { id: 8, + img: '/images/questionImg8.jpg', + questionText: 'Which flower does the spice saffron come from?', + options: ['Orchid', 'Crocus', 'Nasturtium', 'Poppy'], + correctAnswerIndex: 1 }, + + { id: 9, + img: '/images/questionImg9.jpg', + questionText: 'This is the national dish of Spain. What is it called?', + options: ['Risotto', 'Paella', 'Biryani', 'Jambalaya'], + correctAnswerIndex: 1 } ] const initialState = { questions, answers: [], currentQuestionIndex: 0, - quizOver: false + quizOver: null, + btnColor: '', + startTime: 0, + disabledButtons: false, + correctAnswerIndicator: false } export const quiz = createSlice({ @@ -18,50 +74,44 @@ export const quiz = createSlice({ initialState, reducers: { - /** - * Use this action when a user selects an answer to the question. - * The answer will be stored in the `quiz.answers` state with the - * following values: - * - * questionId - The id of the question being answered. - * answerIndex - The index of the selected answer from the question's options. - * question - A copy of the entire question object, to make it easier to show - * details about the question in your UI. - * answer - The answer string. - * isCorrect - true/false if the answer was the one which the question says is correct. - * - * When dispatching this action, you should pass an object as the payload with `questionId` - * and `answerIndex` keys. See the readme for more details. - */ submitAnswer: (state, action) => { const { questionId, answerIndex } = action.payload const question = state.questions.find((q) => q.id === questionId) + let newAnswer = question.options[answerIndex]; + state.correctAnswerIndicator = true + state.disabledButtons = true + + if (newAnswer === undefined || newAnswer === null) { + newAnswer = 'Nothing selected'; + } if (!question) { throw new Error('Could not find question! Check to make sure you are passing the question id correctly.') } - - if (question.options[answerIndex] === undefined) { - throw new Error(`You passed answerIndex ${answerIndex}, but it is not in the possible answers array!`) + if (question.correctAnswerIndex) { + state.btnColor = '#56ab2f' + } + if (question.correctAnswerIndex === answerIndex) { + console.log('correct index', question.correctAnswerIndex, 'selectedIndex', answerIndex) + state.btnColor = '#56ab2f' + } else { + console.log('correct index', question.correctAnswerIndex, 'wrongselectedIndex', answerIndex) + state.btnColor = '#FF416C'; } state.answers.push({ questionId, answerIndex, question, - answer: question.options[answerIndex], + answer: newAnswer, isCorrect: question.correctAnswerIndex === answerIndex }) }, - /** - * Use this action to progress the quiz to the next question. If there's - * no more questions (the user was on the final question), set `quizOver` - * to `true`. - * - * This action does not require a payload. - */ goToNextQuestion: (state) => { + state.disabledButtons = false; + state.btnColor = '' + state.correctAnswerIndicator = false if (state.currentQuestionIndex + 1 === state.questions.length) { state.quizOver = true } else { @@ -69,13 +119,13 @@ export const quiz = createSlice({ } }, - /** - * Use this action to reset the state to the initial state the page had - * when it was loaded. Who doesn't like re-doing a quiz when you know the - * answers?! - * - * This action does not require a payload. - */ + startQuiz: (state) => { + if (state.quizOver === null) { + state.quizOver = false + state.startTime = new Date().getTime() + } + }, + restart: () => { return initialState } diff --git a/src/reusable-components/Button.js b/src/reusable-components/Button.js new file mode 100644 index 00000000..bfa2733a --- /dev/null +++ b/src/reusable-components/Button.js @@ -0,0 +1,54 @@ +import styled from 'styled-components'; + +export const ButtonContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + width: 85vw; + max-width:600px; + gap:2rem; + position:relative; + color: #000;; + + @media (min-width: 768px) { + top:9%; + } + @media (min-width: 1024px) { + flex-wrap: none; + max-width: 85vw; + top:3%; + } +` +export const Button = styled.button` + display: flex; + justify-content: center; + align-items: center; + width: ${(props) => ((props.answerBtn) ? '5.7em' : '12em')}; + height: ${(props) => ((props.answerBtn) ? '3.5em' : '2em')}; + border-radius: 10px; + border:none; + background-color: #dfe3f5; + color:black; + font-family: 'Open Sans', sans-serif; + font-size:100%; + font-weight:500; + cursor: pointer; + + :hover{ + transform: scale(1.15); + } + + + @media (min-width: 768px) { + width: ${(props) => ((props.answerBtn) ? '8em' : '18em')}; + height: ${(props) => ((props.answerBtn) ? '4.5em' : '3em')}; + font-size:1.8rem; + } + + @media (min-width: 1024px) { + width: ${(props) => ((props.answerBtn) ? '8em' : '13em')}; + height: ${(props) => ((props.answerBtn) ? '4.5em' : '2.5em')}; + margin-top:10px; + } +`; \ No newline at end of file diff --git a/src/reusable-components/Image.js b/src/reusable-components/Image.js new file mode 100644 index 00000000..8bdc997a --- /dev/null +++ b/src/reusable-components/Image.js @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +export const Image = styled.img` + display:flex; + max-width:85vw; + max-height:300px; + border:solid black; + border-radius: 15px; + border: 7px solid rgba(181, 118, 221, 0.65); + -webkit-background-clip: padding-box; /* for Safari */ + background-clip: padding-box; /* Firefox 4+, Opera, Chrome */ + +@media (min-width: 1024px) { + max-width:65vw; +} +` \ No newline at end of file diff --git a/src/reusable-components/Title.js b/src/reusable-components/Title.js new file mode 100644 index 00000000..8cccbf85 --- /dev/null +++ b/src/reusable-components/Title.js @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +export const Title = styled.h1` + display: flex; + justify-content: center; + align-items: center; + font-size:${(props) => (props.fontSize)}; + letter-spacing: 0.2rem; + margin:0; + text-shadow: -1px -1px 5px rgba(179, 147, 211, 1); + text-align:center; + max-width: 70vw; + margin: 0 5%; + + @media (min-width: 1024px) { + width:55vw; + font-size:${(props) => ((props.question) ? '2em' : 'auto')}; +} +`; \ No newline at end of file